animation added.

main_production_RSG
Sikander Saleem 1 month ago
parent 729b80d4dc
commit e98f666caf

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -14,16 +12,89 @@ import 'package:test_sa/helper/utils.dart';
import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/views/pages/user/notifications/notifications_page.dart'; import 'package:test_sa/views/pages/user/notifications/notifications_page.dart';
import 'package:test_sa/views/pages/user/notifications/unread_chat_list.dart'; import 'package:test_sa/views/pages/user/notifications/unread_chat_list.dart';
import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart';
import '../../controllers/providers/api/user_provider.dart'; import '../../controllers/providers/api/user_provider.dart';
import '../../models/user.dart'; import '../../models/user.dart';
class AppBarWidget extends StatelessWidget { class AppBarWidget extends StatefulWidget {
final VoidCallback onDrawerPress; // Made nullable final VoidCallback onDrawerPress;
const AppBarWidget({Key? key, required this.onDrawerPress}) : super(key: key); const AppBarWidget({Key? key, required this.onDrawerPress}) : super(key: key);
@override
State<AppBarWidget> createState() => _AppBarWidgetState();
}
class _AppBarWidgetState extends State<AppBarWidget> with TickerProviderStateMixin {
late AnimationController _leftController;
late AnimationController _rightController;
late Animation<Offset> _leftSlideAnimation;
late Animation<double> _leftFadeAnimation;
late Animation<Offset> _rightSlideAnimation;
late Animation<double> _rightFadeAnimation;
@override
void initState() {
super.initState();
// Profile section animation (left to right with fade)
_leftController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_leftSlideAnimation = Tween<Offset>(
begin: const Offset(-0.3, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _leftController,
curve: Curves.easeOutCubic,
));
_leftFadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _leftController,
curve: Curves.easeIn,
));
// Action icons animation (right to left with fade)
_rightController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_rightSlideAnimation = Tween<Offset>(
begin: const Offset(0.3, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _rightController,
curve: Curves.easeOutCubic,
));
_rightFadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _rightController,
curve: Curves.easeIn,
));
// Start animations with slight delay between them
_leftController.forward();
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted) _rightController.forward();
});
}
@override
void dispose() {
_leftController.dispose();
_rightController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(
@ -35,37 +106,55 @@ class AppBarWidget extends StatelessWidget {
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
CircleAvatar( // Profile section with left-to-right fade animation
radius: 24, SlideTransition(
backgroundColor: context.isDark ? AppColor.neutral50 : AppColor.neutral40, position: _leftSlideAnimation,
child: Padding( child: FadeTransition(
padding: const EdgeInsets.all(1), // Border radius opacity: _leftFadeAnimation,
child: ClipOval( child: CircleAvatar(
child: snapshot.profileImage != null radius: 24,
? Image.file(snapshot.profileImage!) // Added null check backgroundColor: context.isDark ? AppColor.neutral50 : AppColor.neutral40,
: (snapshot.user?.profilePhotoName?.isNotEmpty ?? false) child: Padding(
? Image.network(snapshot.user!.profilePhotoName!) // Added null check padding: const EdgeInsets.all(1), // Border radius
: const Icon(Icons.person, size: 24, color: Colors.white), child: ClipOval(
), child: snapshot.profileImage != null
? Image.file(snapshot.profileImage!) // Added null check
: (snapshot.user?.profilePhotoName?.isNotEmpty ?? false)
? Image.network(snapshot.user!.profilePhotoName!) // Added null check
: const Icon(Icons.person, size: 24, color: Colors.white),
),
),
).onPress(widget.onDrawerPress),
), ),
).onPress(onDrawerPress), // Handle potential null ),
8.width, 8.width,
Column( SlideTransition(
crossAxisAlignment: CrossAxisAlignment.start, position: _leftSlideAnimation,
spacing: 4, child: FadeTransition(
children: [ opacity: _leftFadeAnimation,
Text( child: Column(
snapshot.user?.username ?? "", crossAxisAlignment: CrossAxisAlignment.start,
style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null), spacing: 4,
), children: [
Text( Text(
snapshot.user?.type?.name.toCamelCase ?? "", snapshot.user?.username ?? "",
style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null), style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null),
), ),
], Text(
).expanded, snapshot.user?.type?.name.toCamelCase ?? "",
style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null),
),
],
).expanded,
),
),
const Spacer(), const Spacer(),
Consumer<SettingProvider>(builder: (context, settingProvider, child) { // Action icons with right-to-left fade animation
SlideTransition(
position: _rightSlideAnimation,
child: FadeTransition(
opacity: _rightFadeAnimation,
child: Consumer<SettingProvider>(builder: (context, settingProvider, child) {
return MenuAnchor( return MenuAnchor(
style: MenuStyle( style: MenuStyle(
backgroundColor: WidgetStateProperty.all(AppColor.background(context)), backgroundColor: WidgetStateProperty.all(AppColor.background(context)),
@ -229,16 +318,28 @@ class AppBarWidget extends StatelessWidget {
), ),
); );
}), }),
),
),
16.width, 16.width,
if (context.settingProvider.isUserFlowMedical) ...[ SlideTransition(
("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() { position: _rightSlideAnimation,
Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList())); child: FadeTransition(
}), opacity: _rightFadeAnimation,
16.width, child: Row(
], children: [
("dashboard/notification").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() { if (context.settingProvider.isUserFlowMedical) ...[
Navigator.of(context).pushNamed(NotificationsPage.id); ("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() {
}), Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList()));
}),
16.width,
],
("dashboard/notification").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() {
Navigator.of(context).pushNamed(NotificationsPage.id);
}),
],
),
),
),
], ],
).paddingOnly(start: 16, end: 16); ).paddingOnly(start: 16, end: 16);
}), }),

@ -154,7 +154,6 @@ import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart'; import 'package:pie_chart/pie_chart.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/dashboard_latest/dashboard_provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart';
@ -164,11 +163,53 @@ import 'package:test_sa/new_views/app_style/app_color.dart';
import '../../controllers/providers/api/user_provider.dart'; import '../../controllers/providers/api/user_provider.dart';
import '../../models/enums/user_types.dart'; import '../../models/enums/user_types.dart';
class ProgressFragment extends StatelessWidget { class ProgressFragment extends StatefulWidget {
ProgressFragment({Key? key}) : super(key: key); const ProgressFragment({Key? key}) : super(key: key);
@override
State<ProgressFragment> createState() => _ProgressFragmentState();
}
class _ProgressFragmentState extends State<ProgressFragment> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late UserProvider _userProvider; late UserProvider _userProvider;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.6, curve: Curves.easeIn),
));
_scaleAnimation = Tween<double>(
begin: 0.92,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.8, curve: Curves.easeOutCubic),
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_userProvider = Provider.of<UserProvider>(context); _userProvider = Provider.of<UserProvider>(context);
@ -200,107 +241,113 @@ class ProgressFragment extends StatelessWidget {
]; ];
} }
return Column( return FadeTransition(
children: [ opacity: _fadeAnimation,
AspectRatio( child: ScaleTransition(
aspectRatio: (359.toScreenWidth / 214.toScreenWidth), scale: _scaleAnimation,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ AspectRatio(
Text( aspectRatio: (359.toScreenWidth / 214.toScreenWidth),
"Work Order Status".addTranslation, child: Column(
style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)), crossAxisAlignment: CrossAxisAlignment.start,
),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (int i = 0; i < chartData.length; i++)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 6,
width: 6,
decoration: BoxDecoration(color: chartData[i].color, borderRadius: BorderRadius.circular(1)),
),
4.width,
Text(chartData[i].x, style: AppTextStyles.textStyle9.copyWith(color: AppColor.text9Color(context))),
],
)
],
),
8.height,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
spacing: 60.toScreenWidth,
children: [ children: [
Expanded( Text(
flex: 3, "Work Order Status".addTranslation,
child: SizedBox( style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)),
height: 124.toScreenWidth, ),
width: 124.toScreenWidth, Wrap(
child: PieChart( spacing: 8,
dataMap: {for (var v in chartData) v.x: v.y}, runSpacing: 8,
animationDuration: const Duration(milliseconds: 750), children: [
chartLegendSpacing: 0, for (int i = 0; i < chartData.length; i++)
chartRadius: 124, Row(
colorList: chartData.map((element) => element.color).toList(), mainAxisSize: MainAxisSize.min,
initialAngleInDegree: 270, children: [
chartType: ChartType.ring, Container(
ringStrokeWidth: 32, height: 6,
legendOptions: LegendOptions( width: 6,
showLegendsInRow: true, decoration: BoxDecoration(color: chartData[i].color, borderRadius: BorderRadius.circular(1)),
legendPosition: LegendPosition.top, ),
showLegends: false, 4.width,
legendTextStyle: AppTextStyles.textStyle9, Text(chartData[i].x, style: AppTextStyles.textStyle9.copyWith(color: AppColor.text9Color(context))),
), ],
chartValuesOptions: ChartValuesOptions( )
showChartValueBackground: true, ],
chartValueBackgroundColor: const Color(0xff226187).withValues(alpha: .75),
chartValueStyle: AppTextStyles.tinyFont2.copyWith(height: 1.5, color: Colors.white),
showChartValues: true,
showChartValuesInPercentage: true,
showChartValuesOutside: true,
decimalPlaces: 0,
),
).toShimmer(isShow: snapshot.isAllCountLoading, radius: 250, context: context).paddingAll(0),
),
), ),
Expanded( 8.height,
flex: 2, Row(
child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisAlignment: MainAxisAlignment.end, spacing: 60.toScreenWidth,
crossAxisAlignment: CrossAxisAlignment.start, children: [
spacing: 8, Expanded(
children: [ flex: 3,
for (int i = 0; i < chartData.length; i++) child: SizedBox(
Row( height: 124.toScreenWidth,
spacing: 16, width: 124.toScreenWidth,
children: [ child: PieChart(
chartData[i].icon.toSvgAsset(height: 26, width: 26, color: chartData[i].color).toShimmer(isShow: snapshot.isAllCountLoading, context: context), dataMap: {for (var v in chartData) v.x: v.y},
Column( animationDuration: const Duration(milliseconds: 750),
spacing: 4, chartLegendSpacing: 0,
crossAxisAlignment: CrossAxisAlignment.start, chartRadius: 124,
colorList: chartData.map((element) => element.color).toList(),
initialAngleInDegree: 270,
chartType: ChartType.ring,
ringStrokeWidth: 32,
legendOptions: LegendOptions(
showLegendsInRow: true,
legendPosition: LegendPosition.top,
showLegends: false,
legendTextStyle: AppTextStyles.textStyle9,
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
chartValueBackgroundColor: const Color(0xff226187).withValues(alpha: .75),
chartValueStyle: AppTextStyles.tinyFont2.copyWith(height: 1.5, color: Colors.white),
showChartValues: true,
showChartValuesInPercentage: true,
showChartValuesOutside: true,
decimalPlaces: 0,
),
).toShimmer(isShow: snapshot.isAllCountLoading, radius: 250, context: context).paddingAll(0),
),
),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
for (int i = 0; i < chartData.length; i++)
Row(
spacing: 16,
children: [ children: [
Text(chartData[i].x, style: AppTextStyles.textStyle9.copyWith(color: AppColor.text9Color(context))) chartData[i].icon.toSvgAsset(height: 26, width: 26, color: chartData[i].color).toShimmer(isShow: snapshot.isAllCountLoading, context: context),
.toShimmer(isShow: snapshot.isAllCountLoading, context: context), Column(
Text("${chartData[i].y.toInt()}", style: AppTextStyles.bodyText.copyWith(color: AppColor.headingTextColor(context))) spacing: 4,
.toShimmer(isShow: snapshot.isAllCountLoading, context: context), crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(chartData[i].x, style: AppTextStyles.textStyle9.copyWith(color: AppColor.text9Color(context)))
.toShimmer(isShow: snapshot.isAllCountLoading, context: context),
Text("${chartData[i].y.toInt()}", style: AppTextStyles.bodyText.copyWith(color: AppColor.headingTextColor(context)))
.toShimmer(isShow: snapshot.isAllCountLoading, context: context),
],
)
], ],
) )
], ],
) ),
], ),
), ],
), ).expanded,
], ],
).expanded, ).toShadowContainer(context, borderRadius: 20, paddingObject: const EdgeInsets.symmetric(horizontal: 24, vertical: 16)),
], ),
).toShadowContainer(context, borderRadius: 20, paddingObject: const EdgeInsets.symmetric(horizontal: 24, vertical: 16)), ],
), ).paddingOnly(start: 16, end: 16),
], ),
).paddingOnly(start: 16, end: 16); );
}, },
); );
} }

@ -1,4 +1,3 @@
import 'package:date_picker_timeline/extra/color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart';
@ -23,8 +22,25 @@ class RequestCategoryFragment extends StatefulWidget {
State<RequestCategoryFragment> createState() => _RequestCategoryFragmentState(); State<RequestCategoryFragment> createState() => _RequestCategoryFragmentState();
} }
class _RequestCategoryFragmentState extends State<RequestCategoryFragment> { class _RequestCategoryFragmentState extends State<RequestCategoryFragment> with SingleTickerProviderStateMixin {
bool isAscending = false; bool isAscending = false;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggleSort(DashBoardProvider dashboardProvider, UsersTypes? userType) { void _toggleSort(DashBoardProvider dashboardProvider, UsersTypes? userType) {
setState(() { setState(() {
@ -163,28 +179,61 @@ class _RequestCategoryFragmentState extends State<RequestCategoryFragment> {
shrinkWrap: true, shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: context.isTablet() ? 5 : 4, childAspectRatio: 85 / 85, crossAxisSpacing: 8, mainAxisSpacing: 8), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: context.isTablet() ? 5 : 4, childAspectRatio: 85 / 85, crossAxisSpacing: 8, mainAxisSpacing: 8),
itemBuilder: (cxt, index) { itemBuilder: (cxt, index) {
return RequestTypeButton( // Calculate staggered animation delay
label: tabs[index].label, final delay = index * 0.08;
loading: requestsProvider.isDetailLoading && requestsProvider.currentListIndex == index, final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
isSelected: requestsProvider.currentListIndex == index, CurvedAnimation(
count: requestsProvider.getStatusCount(tabs[index].requestType), parent: _animationController,
onPressed: () async { curve: Interval(
setState(() { delay.clamp(0.0, 0.8),
isAscending = false; (delay + 0.3).clamp(0.0, 1.0),
}); curve: Curves.easeOut,
requestsProvider.currentListIndex = index; ),
requestsProvider.resetRequestDataList(); ),
requestsProvider.getRequestDetail( );
usersType: userType ?? UsersTypes.normal_user,
showLoader: true, final slideAnimation = Tween<Offset>(
status: tabs[index].tag, begin: const Offset(0.0, 0.3),
tabId: tabs[index].id, end: Offset.zero,
isHighPriority: tabs[index].isHighPriority, ).animate(
isOverdue: tabs[index].isOverDue, CurvedAnimation(
); parent: _animationController,
}, curve: Interval(
icon: tabs[index].icon, delay.clamp(0.0, 0.8),
iconColor: tabs[index].iconColor, (delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: RequestTypeButton(
label: tabs[index].label,
loading: requestsProvider.isDetailLoading && requestsProvider.currentListIndex == index,
isSelected: requestsProvider.currentListIndex == index,
count: requestsProvider.getStatusCount(tabs[index].requestType),
onPressed: () async {
setState(() {
isAscending = false;
});
requestsProvider.currentListIndex = index;
requestsProvider.resetRequestDataList();
requestsProvider.getRequestDetail(
usersType: userType ?? UsersTypes.normal_user,
showLoader: true,
status: tabs[index].tag,
tabId: tabs[index].id,
isHighPriority: tabs[index].isHighPriority,
isOverdue: tabs[index].isOverDue,
);
},
icon: tabs[index].icon,
iconColor: tabs[index].iconColor,
),
),
); );
}, },
itemCount: tabs.length, itemCount: tabs.length,

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
@ -19,26 +17,83 @@ import 'package:test_sa/new_views/pages/land_page/requests/ppm_item_view.dart';
import 'package:test_sa/new_views/pages/land_page/requests/recurrent_wo_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/recurrent_wo_item_view.dart';
import 'package:test_sa/new_views/pages/land_page/requests/service_request_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/service_request_item_view.dart';
class RequestCategoryList extends StatelessWidget { class RequestCategoryList extends StatefulWidget {
final List<Data> list; final List<Data> list;
final int totalCount; final int totalCount;
final bool isLoading; final bool isLoading;
const RequestCategoryList(this.list, this.isLoading, this.totalCount, {Key? key}) : super(key: key); const RequestCategoryList(this.list, this.isLoading, this.totalCount, {Key? key}) : super(key: key);
@override
State<RequestCategoryList> createState() => _RequestCategoryListState();
}
class _RequestCategoryListState extends State<RequestCategoryList> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.separated( return ListView.separated(
itemCount: list.length < totalCount ? list.length + 1 : list.length, itemCount: widget.list.length < widget.totalCount ? widget.list.length + 1 : widget.list.length,
shrinkWrap: true, shrinkWrap: true,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (cxt, index) => 12.height, separatorBuilder: (cxt, index) => 12.height,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == list.length) { if (index == widget.list.length) {
return CircularProgressIndicator(color: AppColor.loadingColor(context), strokeWidth: 1).center; return CircularProgressIndicator(color: AppColor.loadingColor(context), strokeWidth: 1).center;
} }
return _buildRequestItem(list[index]);
// Calculate staggered animation delay
final delay = index * 0.08;
final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.7),
(delay + 0.4).clamp(0.0, 1.0),
curve: Curves.easeOut,
),
),
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.15),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.7),
(delay + 0.4).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: _buildRequestItem(widget.list[index]),
),
);
}, },
); );
} }

@ -22,9 +22,32 @@ import 'package:test_sa/modules/traf_module/create_traf_request_page.dart';
import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/modules/tm_module/gas_refill/gas_refill_request_form.dart'; import 'package:test_sa/modules/tm_module/gas_refill/gas_refill_request_form.dart';
class CreateRequestTypeBottomSheet extends StatelessWidget { class CreateRequestTypeBottomSheet extends StatefulWidget {
const CreateRequestTypeBottomSheet({super.key}); const CreateRequestTypeBottomSheet({super.key});
@override
State<CreateRequestTypeBottomSheet> createState() => _CreateRequestTypeBottomSheetState();
}
class _CreateRequestTypeBottomSheetState extends State<CreateRequestTypeBottomSheet> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<CreateRequestModel> requestList = CreateRequestModel.requestsList(context); List<CreateRequestModel> requestList = CreateRequestModel.requestsList(context);
@ -40,28 +63,60 @@ class CreateRequestTypeBottomSheet extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
context.translation.selectWorkOrder.heading4(context), context.translation.selectWorkOrder.heading4(context),
GridView( GridView.builder(
padding: const EdgeInsets.only(top: 16, bottom: 0), padding: const EdgeInsets.only(top: 16, bottom: 0),
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16),
children: <Widget>[ itemCount: requestList.length,
for (var request in requestList) itemBuilder: (context, index) {
listCard( // Calculate staggered animation delay
context: context, final delay = index * 0.08;
icon: request.icon, final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
label: request.title, CurvedAnimation(
onTap: () { parent: _animationController,
Navigator.pushReplacementNamed(context, request.routeName); curve: Interval(
}, delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOut,
),
),
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: listCard(
context: context,
icon: requestList[index].icon,
label: requestList[index].title,
onTap: () {
Navigator.pushReplacementNamed(context, requestList[index].routeName);
},
),
), ),
], );
},
), ),
], ],
), ),
), ),
); );
return const Placeholder();
} }
Widget listCard({required String icon, required String label, required VoidCallback onTap, required BuildContext context}) { Widget listCard({required String icon, required String label, required VoidCallback onTap, required BuildContext context}) {

@ -31,12 +31,51 @@ class MyRequestsPage extends StatefulWidget {
State<MyRequestsPage> createState() => _MyRequestsPageState(); State<MyRequestsPage> createState() => _MyRequestsPageState();
} }
class _MyRequestsPageState extends State<MyRequestsPage> { class _MyRequestsPageState extends State<MyRequestsPage> with SingleTickerProviderStateMixin {
AllRequestsProvider? _provider; AllRequestsProvider? _provider;
SearchAllRequestsModel? _search; SearchAllRequestsModel? _search;
late List<Request> requestsList; late List<Request> requestsList;
int selectedRequest = 0; int selectedRequest = 0;
late AnimationController _tabAnimationController;
late Animation<double> _tabContainerAnimation;
late Animation<Offset> _tabContainerSlideAnimation;
int _listAnimationKey = 0; // Key to force list re-animation
@override
void initState() {
super.initState();
_tabAnimationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
// Container entrance animation (faster than individual tabs)
_tabContainerAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _tabAnimationController,
curve: const Interval(0.0, 0.4, curve: Curves.easeOut),
),
);
_tabContainerSlideAnimation = Tween<Offset>(
begin: const Offset(0.0, -0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _tabAnimationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOutCubic),
),
);
_tabAnimationController.forward();
}
@override
void dispose() {
_tabAnimationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -190,59 +229,93 @@ class _MyRequestsPageState extends State<MyRequestsPage> {
children: [ children: [
if (!_filterIsEmpty()) _buildActiveFiltersChips(), if (!_filterIsEmpty()) _buildActiveFiltersChips(),
if (_filterIsEmpty()) if (_filterIsEmpty())
SizedBox( FadeTransition(
height: 50.toScreenHeight, opacity: _tabContainerAnimation,
child: ListView.separated( child: SlideTransition(
scrollDirection: Axis.horizontal, position: _tabContainerSlideAnimation,
padding: const EdgeInsets.only(top: 16, right: 16, left: 16), child: SizedBox(
itemBuilder: (cxt, index) => Container( height: 50.toScreenHeight,
alignment: Alignment.center, child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12), scrollDirection: Axis.horizontal,
// foregroundDecoration: selectedRequest != index padding: const EdgeInsets.only(top: 16, right: 16, left: 16),
// ? null itemBuilder: (cxt, index) {
// : ShapeDecoration( // Calculate staggered animation delay
// color: (AppColor.red20), final delay = index * 0.08;
// shape: RoundedRectangleBorder( final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
// side: BorderSide(width: 1, color: AppColor.blueStatus(context)), CurvedAnimation(
// borderRadius: BorderRadius.circular(7), parent: _tabAnimationController,
// ), curve: Interval(
// ), delay.clamp(0.0, 0.8),
decoration: ShapeDecoration( (delay + 0.3).clamp(0.0, 1.0),
color: //selectedRequest == index ? curve: Curves.easeOut,
AppColor.background(context), //: AppColor.unSelectedButtonColor(context).withOpacity(snapshot.isAllLoading ? 0.7 : 1), ),
shape: RoundedRectangleBorder( ),
side: BorderSide(width: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.borderColor(context)), );
borderRadius: BorderRadius.circular(6),
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _tabAnimationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
), ),
shadows: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 14, offset: const Offset(0, 0), spreadRadius: 0)], );
),
child: Text( return FadeTransition(
requestsList[index].value, opacity: animation,
style: AppTextStyles.tinyFont.copyWith(height: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.filterButtonTextColor(context)), child: SlideTransition(
), position: slideAnimation,
).onPress(() { child: Container(
if (snapshot.isAllLoading) { alignment: Alignment.center,
return; padding: const EdgeInsets.symmetric(horizontal: 12),
} decoration: ShapeDecoration(
color: AppColor.background(context),
if (selectedRequest != index) { shape: RoundedRectangleBorder(
setState(() { side: BorderSide(width: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.borderColor(context)),
selectedRequest = index; borderRadius: BorderRadius.circular(6),
}); ),
shadows: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 14, offset: const Offset(0, 0), spreadRadius: 0)],
_provider!.reset(); ),
_provider!.getAllRequests(context, typeTransaction: requestsList[selectedRequest].id); child: Text(
} requestsList[index].value,
}), style: AppTextStyles.tinyFont.copyWith(height: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.filterButtonTextColor(context)),
separatorBuilder: (cxt, index) => 8.width, ),
itemCount: requestsList.length), ).onPress(() {
if (snapshot.isAllLoading) {
return;
}
setState(() {
selectedRequest = index;
_listAnimationKey++; // Force list to re-animate
});
_provider!.reset();
_provider!.getAllRequests(context, typeTransaction: requestsList[selectedRequest].id);
}),
),
);
},
separatorBuilder: (cxt, index) => 8.width,
itemCount: requestsList.length),
),
),
), ),
LazyLoading( LazyLoading(
nextPage: snapshot.nextPage, nextPage: snapshot.nextPage,
onLazyLoad: () async { onLazyLoad: () async {
snapshot.getAllRequests(context, typeTransaction: requestsList[selectedRequest].id, search: _search); snapshot.getAllRequests(context, typeTransaction: requestsList[selectedRequest].id, search: _search);
}, },
child: RequestItemViewList(snapshot.allRequestsAndCount?.requestsDetails ?? [], snapshot.isAllLoading), child: RequestItemViewList(
snapshot.allRequestsAndCount?.requestsDetails ?? [],
snapshot.isAllLoading,
key: ValueKey('request_list_${selectedRequest}_$_listAnimationKey'), // Key changes on every tab click
),
).expanded, ).expanded,
], ],
); );

@ -1,5 +1,4 @@
//request tab page //request tab page
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
@ -16,7 +15,6 @@ import 'package:test_sa/modules/loan_module/pages/loan_equipment_item_view.dart'
import 'package:test_sa/modules/medical_gas_inspection/medical_gas_inspection_item_view.dart'; import 'package:test_sa/modules/medical_gas_inspection/medical_gas_inspection_item_view.dart';
import 'package:test_sa/modules/tm_module/tasks/task_request_item_view.dart'; import 'package:test_sa/modules/tm_module/tasks/task_request_item_view.dart';
import 'package:test_sa/modules/traf_module/traf_request_item_view.dart'; import 'package:test_sa/modules/traf_module/traf_request_item_view.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/pages/land_page/requests/device_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/device_item_view.dart';
import 'package:test_sa/new_views/pages/land_page/requests/gas_refill_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/gas_refill_item_view.dart';
import 'package:test_sa/new_views/pages/land_page/requests/ppm_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/ppm_item_view.dart';
@ -24,67 +22,141 @@ import 'package:test_sa/new_views/pages/land_page/requests/recurrent_wo_item_vie
import 'package:test_sa/new_views/pages/land_page/requests/service_request_item_view.dart'; import 'package:test_sa/new_views/pages/land_page/requests/service_request_item_view.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
class RequestItemViewList extends StatelessWidget { class RequestItemViewList extends StatefulWidget {
List<RequestsDetails> list; final List<RequestsDetails> list;
bool isLoading; final bool isLoading;
RequestItemViewList(this.list, this.isLoading, {Key? key}) : super(key: key); const RequestItemViewList(this.list, this.isLoading, {Key? key}) : super(key: key);
@override
State<RequestItemViewList> createState() => _RequestItemViewListState();
}
class _RequestItemViewListState extends State<RequestItemViewList> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return (list.isEmpty && !isLoading) return (widget.list.isEmpty && !widget.isLoading)
? NoDataFound(message: context.translation.noDataFound).center ? NoDataFound(message: context.translation.noDataFound).center
: ListView.separated( : ListView.separated(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
shrinkWrap: true, shrinkWrap: true,
itemBuilder: (cxt, index) { itemBuilder: (cxt, index) {
if (isLoading) return const SizedBox().toRequestShimmer(cxt, isLoading); if (widget.isLoading) return const SizedBox().toRequestShimmer(cxt, widget.isLoading);
// Calculate staggered animation delay
final delay = index * 0.08;
final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOut,
),
),
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
switch (list[index].transactionType) { Widget itemWidget;
switch (widget.list[index].transactionType) {
case 1: case 1:
return ServiceRequestItemView(requestDetails: list[index]); itemWidget = ServiceRequestItemView(requestDetails: widget.list[index]);
// case 1: break;
// return InternalAuditItemView(requestDetails: list[index]);
case 2: case 2:
return GasRefillItemView(requestDetails: list[index]); itemWidget = GasRefillItemView(requestDetails: widget.list[index]);
break;
case 3: case 3:
return DeviceItemView(requestDetails: list[index]); itemWidget = DeviceItemView(requestDetails: widget.list[index]);
break;
case 4: case 4:
return PpmItemView(requestDetails: list[index]); itemWidget = PpmItemView(requestDetails: widget.list[index]);
break;
case 5: case 5:
return RecurrentWoItemView(requestDetails: list[index]); itemWidget = RecurrentWoItemView(requestDetails: widget.list[index]);
break;
case 6: case 6:
return TaskRequestItemView(requestDetails: list[index]); itemWidget = TaskRequestItemView(requestDetails: widget.list[index]);
break;
case 7: case 7:
return TaskRequestItemView(requestDetails: list[index]); itemWidget = TaskRequestItemView(requestDetails: widget.list[index]);
break;
case 8: case 8:
return InventorySessionItemView(requestDetails: list[index]); itemWidget = InventorySessionItemView(requestDetails: widget.list[index]);
break;
case 9: case 9:
return TrafRequestItemView(requestDetails: list[index]); itemWidget = TrafRequestItemView(requestDetails: widget.list[index]);
//Need to verify the type No.. break;
case 10: case 10:
return EquipmentInternalAuditItemView(requestDetails: list[index]); itemWidget = EquipmentInternalAuditItemView(requestDetails: widget.list[index]);
break;
case 11: case 11:
return SystemInternalAuditItemView(requestDetails: list[index]); itemWidget = SystemInternalAuditItemView(requestDetails: widget.list[index]);
break;
case 12: case 12:
return LoanEquipmentItemView(requestDetails: list[index]); itemWidget = LoanEquipmentItemView(requestDetails: widget.list[index]);
break;
case 13: case 13:
return AssetDeliveryItemView(requestDetails: list[index]); itemWidget = AssetDeliveryItemView(requestDetails: widget.list[index]);
break;
case 14: case 14:
return MedicalGasInspectionItemView(requestDetails: list[index]); itemWidget = MedicalGasInspectionItemView(requestDetails: widget.list[index]);
break;
case 15: case 15:
return DemoRequestItemView(requestDetails: list[index]); itemWidget = DemoRequestItemView(requestDetails: widget.list[index]);
break;
case 16: case 16:
return IncidentItemView(requestDetails: list[index]); itemWidget = IncidentItemView(requestDetails: widget.list[index]);
break;
default: default:
Container( itemWidget = Container(
height: 100, height: 100,
width: double.infinity, width: double.infinity,
color: Colors.grey, color: Colors.grey,
); );
} }
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: itemWidget,
),
);
}, },
separatorBuilder: (cxt, index) => 12.height, separatorBuilder: (cxt, index) => 12.height,
itemCount: isLoading ? 6 : list.length); itemCount: widget.isLoading ? 6 : widget.list.length);
} }
} }

@ -35,7 +35,7 @@ class MyAssetsPage extends StatefulWidget {
_MyAssetsPageState createState() => _MyAssetsPageState(); _MyAssetsPageState createState() => _MyAssetsPageState();
} }
class _MyAssetsPageState extends State<MyAssetsPage> { class _MyAssetsPageState extends State<MyAssetsPage> with SingleTickerProviderStateMixin {
late AssetProvider _devicesProvider; late AssetProvider _devicesProvider;
late UserProvider _userProvider; late UserProvider _userProvider;
late SettingProvider _settingProvider; late SettingProvider _settingProvider;
@ -45,6 +45,7 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
DeviceSearch? _searchAsset; DeviceSearch? _searchAsset;
bool showListView = true; bool showListView = true;
late AnimationController _animationController;
TextEditingController numberController = TextEditingController(); TextEditingController numberController = TextEditingController();
TextEditingController snController = TextEditingController(); TextEditingController snController = TextEditingController();
@ -59,12 +60,18 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
} }
@override @override
void dispose() { void dispose() {
numberController.dispose(); numberController.dispose();
snController.dispose(); snController.dispose();
_animationController.dispose();
_devicesProvider.reset(); _devicesProvider.reset();
super.dispose(); super.dispose();
} }
@ -193,29 +200,96 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: showListView child: showListView
? ListView.separated( ? ListView.separated(
key: const ValueKey('list_view'),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemCount: _searchableList.length, itemCount: _searchableList.length,
separatorBuilder: (listContext, itemIndex) => 12.height, separatorBuilder: (listContext, itemIndex) => 12.height,
itemBuilder: (listContext, itemIndex) { itemBuilder: (listContext, itemIndex) {
return AssetItemListView( // Calculate staggered animation delay
device: _searchableList[itemIndex], final delay = itemIndex * 0.08;
onPressed: (device) { final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device); CurvedAnimation(
// Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id); parent: _animationController,
}, curve: Interval(
delay.clamp(0.0, 0.7),
(delay + 0.4).clamp(0.0, 1.0),
curve: Curves.easeOut,
),
),
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.15),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.7),
(delay + 0.4).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: AssetItemListView(
device: _searchableList[itemIndex],
onPressed: (device) {
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device);
},
),
),
); );
}, },
) )
: GridView.builder( : GridView.builder(
key: const ValueKey('grid_view'),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 188 / 261, crossAxisSpacing: 12, mainAxisSpacing: 12), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 188 / 261, crossAxisSpacing: 12, mainAxisSpacing: 12),
itemCount: _searchableList.length, itemCount: _searchableList.length,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AssetItemGridView( // Calculate staggered animation delay
device: _searchableList[index], final delay = index * 0.08;
onPressed: (device) { final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device); CurvedAnimation(
}, parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOut,
),
),
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(
delay.clamp(0.0, 0.8),
(delay + 0.3).clamp(0.0, 1.0),
curve: Curves.easeOutCubic,
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: AssetItemGridView(
device: _searchableList[index],
onPressed: (device) {
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device);
},
),
),
); );
}, },
), ),

Loading…
Cancel
Save