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/material.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/views/pages/user/notifications/notifications_page.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 '../../models/user.dart';
class AppBarWidget extends StatelessWidget {
final VoidCallback onDrawerPress; // Made nullable
class AppBarWidget extends StatefulWidget {
final VoidCallback onDrawerPress;
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
Widget build(BuildContext context) {
return AppBar(
@ -35,37 +106,55 @@ class AppBarWidget extends StatelessWidget {
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
radius: 24,
backgroundColor: context.isDark ? AppColor.neutral50 : AppColor.neutral40,
child: Padding(
padding: const EdgeInsets.all(1), // Border radius
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),
),
// Profile section with left-to-right fade animation
SlideTransition(
position: _leftSlideAnimation,
child: FadeTransition(
opacity: _leftFadeAnimation,
child: CircleAvatar(
radius: 24,
backgroundColor: context.isDark ? AppColor.neutral50 : AppColor.neutral40,
child: Padding(
padding: const EdgeInsets.all(1), // Border radius
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,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
Text(
snapshot.user?.username ?? "",
style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null),
),
Text(
snapshot.user?.type?.name.toCamelCase ?? "",
style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null),
),
],
).expanded,
SlideTransition(
position: _leftSlideAnimation,
child: FadeTransition(
opacity: _leftFadeAnimation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
Text(
snapshot.user?.username ?? "",
style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null),
),
Text(
snapshot.user?.type?.name.toCamelCase ?? "",
style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null),
),
],
).expanded,
),
),
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(
style: MenuStyle(
backgroundColor: WidgetStateProperty.all(AppColor.background(context)),
@ -229,16 +318,28 @@ class AppBarWidget extends StatelessWidget {
),
);
}),
),
),
16.width,
if (context.settingProvider.isUserFlowMedical) ...[
("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);
}),
SlideTransition(
position: _rightSlideAnimation,
child: FadeTransition(
opacity: _rightFadeAnimation,
child: Row(
children: [
if (context.settingProvider.isUserFlowMedical) ...[
("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);
}),

@ -154,7 +154,6 @@ import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'package:provider/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/string_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 '../../models/enums/user_types.dart';
class ProgressFragment extends StatelessWidget {
ProgressFragment({Key? key}) : super(key: key);
class ProgressFragment extends StatefulWidget {
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;
@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
Widget build(BuildContext context) {
_userProvider = Provider.of<UserProvider>(context);
@ -200,107 +241,113 @@ class ProgressFragment extends StatelessWidget {
];
}
return Column(
children: [
AspectRatio(
aspectRatio: (359.toScreenWidth / 214.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Work Order Status".addTranslation,
style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)),
),
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,
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: Column(
children: [
AspectRatio(
aspectRatio: (359.toScreenWidth / 214.toScreenWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: SizedBox(
height: 124.toScreenWidth,
width: 124.toScreenWidth,
child: PieChart(
dataMap: {for (var v in chartData) v.x: v.y},
animationDuration: const Duration(milliseconds: 750),
chartLegendSpacing: 0,
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),
),
Text(
"Work Order Status".addTranslation,
style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)),
),
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))),
],
)
],
),
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: [
chartData[i].icon.toSvgAsset(height: 26, width: 26, color: chartData[i].color).toShimmer(isShow: snapshot.isAllCountLoading, context: context),
Column(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
8.height,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
spacing: 60.toScreenWidth,
children: [
Expanded(
flex: 3,
child: SizedBox(
height: 124.toScreenWidth,
width: 124.toScreenWidth,
child: PieChart(
dataMap: {for (var v in chartData) v.x: v.y},
animationDuration: const Duration(milliseconds: 750),
chartLegendSpacing: 0,
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: [
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),
chartData[i].icon.toSvgAsset(height: 26, width: 26, color: chartData[i].color).toShimmer(isShow: snapshot.isAllCountLoading, context: context),
Column(
spacing: 4,
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)),
),
],
).paddingOnly(start: 16, end: 16);
).toShadowContainer(context, borderRadius: 20, paddingObject: const EdgeInsets.symmetric(horizontal: 24, vertical: 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:provider/provider.dart';
import 'package:test_sa/controllers/providers/api/user_provider.dart';
@ -23,8 +22,25 @@ class RequestCategoryFragment extends StatefulWidget {
State<RequestCategoryFragment> createState() => _RequestCategoryFragmentState();
}
class _RequestCategoryFragmentState extends State<RequestCategoryFragment> {
class _RequestCategoryFragmentState extends State<RequestCategoryFragment> with SingleTickerProviderStateMixin {
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) {
setState(() {
@ -163,28 +179,61 @@ class _RequestCategoryFragmentState extends State<RequestCategoryFragment> {
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: context.isTablet() ? 5 : 4, childAspectRatio: 85 / 85, crossAxisSpacing: 8, mainAxisSpacing: 8),
itemBuilder: (cxt, index) {
return 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,
// 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,
),
),
);
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,

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/int_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/service_request_item_view.dart';
class RequestCategoryList extends StatelessWidget {
class RequestCategoryList extends StatefulWidget {
final List<Data> list;
final int totalCount;
final bool isLoading;
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
Widget build(BuildContext context) {
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,
padding: const EdgeInsets.all(16),
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (cxt, index) => 12.height,
itemBuilder: (context, index) {
if (index == list.length) {
return CircularProgressIndicator(color: AppColor.loadingColor(context), strokeWidth: 1).center;
if (index == widget.list.length) {
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/modules/tm_module/gas_refill/gas_refill_request_form.dart';
class CreateRequestTypeBottomSheet extends StatelessWidget {
class CreateRequestTypeBottomSheet extends StatefulWidget {
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
Widget build(BuildContext context) {
List<CreateRequestModel> requestList = CreateRequestModel.requestsList(context);
@ -40,28 +63,60 @@ class CreateRequestTypeBottomSheet extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
context.translation.selectWorkOrder.heading4(context),
GridView(
GridView.builder(
padding: const EdgeInsets.only(top: 16, bottom: 0),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16),
children: <Widget>[
for (var request in requestList)
listCard(
context: context,
icon: request.icon,
label: request.title,
onTap: () {
Navigator.pushReplacementNamed(context, request.routeName);
},
itemCount: requestList.length,
itemBuilder: (context, 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.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}) {

@ -31,12 +31,51 @@ class MyRequestsPage extends StatefulWidget {
State<MyRequestsPage> createState() => _MyRequestsPageState();
}
class _MyRequestsPageState extends State<MyRequestsPage> {
class _MyRequestsPageState extends State<MyRequestsPage> with SingleTickerProviderStateMixin {
AllRequestsProvider? _provider;
SearchAllRequestsModel? _search;
late List<Request> requestsList;
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
Widget build(BuildContext context) {
@ -190,59 +229,93 @@ class _MyRequestsPageState extends State<MyRequestsPage> {
children: [
if (!_filterIsEmpty()) _buildActiveFiltersChips(),
if (_filterIsEmpty())
SizedBox(
height: 50.toScreenHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(top: 16, right: 16, left: 16),
itemBuilder: (cxt, index) => Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 12),
// foregroundDecoration: selectedRequest != index
// ? null
// : ShapeDecoration(
// color: (AppColor.red20),
// shape: RoundedRectangleBorder(
// side: BorderSide(width: 1, color: AppColor.blueStatus(context)),
// borderRadius: BorderRadius.circular(7),
// ),
// ),
decoration: ShapeDecoration(
color: //selectedRequest == index ?
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),
FadeTransition(
opacity: _tabContainerAnimation,
child: SlideTransition(
position: _tabContainerSlideAnimation,
child: SizedBox(
height: 50.toScreenHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(top: 16, right: 16, left: 16),
itemBuilder: (cxt, index) {
// Calculate staggered animation delay
final delay = index * 0.08;
final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _tabAnimationController,
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: _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(
requestsList[index].value,
style: AppTextStyles.tinyFont.copyWith(height: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.filterButtonTextColor(context)),
),
).onPress(() {
if (snapshot.isAllLoading) {
return;
}
if (selectedRequest != index) {
setState(() {
selectedRequest = index;
});
_provider!.reset();
_provider!.getAllRequests(context, typeTransaction: requestsList[selectedRequest].id);
}
}),
separatorBuilder: (cxt, index) => 8.width,
itemCount: requestsList.length),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: ShapeDecoration(
color: AppColor.background(context),
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.borderColor(context)),
borderRadius: BorderRadius.circular(6),
),
shadows: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 14, offset: const Offset(0, 0), spreadRadius: 0)],
),
child: Text(
requestsList[index].value,
style: AppTextStyles.tinyFont.copyWith(height: 1, color: selectedRequest == index ? AppColor.primary10 : AppColor.filterButtonTextColor(context)),
),
).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(
nextPage: snapshot.nextPage,
onLazyLoad: () async {
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,
],
);

@ -1,5 +1,4 @@
//request tab page
import 'dart:developer';
import 'package:flutter/material.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/tm_module/tasks/task_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/gas_refill_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/views/widgets/loaders/no_data_found.dart';
class RequestItemViewList extends StatelessWidget {
List<RequestsDetails> list;
bool isLoading;
class RequestItemViewList extends StatefulWidget {
final List<RequestsDetails> list;
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
Widget build(BuildContext context) {
return (list.isEmpty && !isLoading)
return (widget.list.isEmpty && !widget.isLoading)
? NoDataFound(message: context.translation.noDataFound).center
: ListView.separated(
padding: const EdgeInsets.all(16),
shrinkWrap: true,
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:
return ServiceRequestItemView(requestDetails: list[index]);
// case 1:
// return InternalAuditItemView(requestDetails: list[index]);
itemWidget = ServiceRequestItemView(requestDetails: widget.list[index]);
break;
case 2:
return GasRefillItemView(requestDetails: list[index]);
itemWidget = GasRefillItemView(requestDetails: widget.list[index]);
break;
case 3:
return DeviceItemView(requestDetails: list[index]);
itemWidget = DeviceItemView(requestDetails: widget.list[index]);
break;
case 4:
return PpmItemView(requestDetails: list[index]);
itemWidget = PpmItemView(requestDetails: widget.list[index]);
break;
case 5:
return RecurrentWoItemView(requestDetails: list[index]);
itemWidget = RecurrentWoItemView(requestDetails: widget.list[index]);
break;
case 6:
return TaskRequestItemView(requestDetails: list[index]);
itemWidget = TaskRequestItemView(requestDetails: widget.list[index]);
break;
case 7:
return TaskRequestItemView(requestDetails: list[index]);
itemWidget = TaskRequestItemView(requestDetails: widget.list[index]);
break;
case 8:
return InventorySessionItemView(requestDetails: list[index]);
itemWidget = InventorySessionItemView(requestDetails: widget.list[index]);
break;
case 9:
return TrafRequestItemView(requestDetails: list[index]);
//Need to verify the type No..
itemWidget = TrafRequestItemView(requestDetails: widget.list[index]);
break;
case 10:
return EquipmentInternalAuditItemView(requestDetails: list[index]);
itemWidget = EquipmentInternalAuditItemView(requestDetails: widget.list[index]);
break;
case 11:
return SystemInternalAuditItemView(requestDetails: list[index]);
itemWidget = SystemInternalAuditItemView(requestDetails: widget.list[index]);
break;
case 12:
return LoanEquipmentItemView(requestDetails: list[index]);
itemWidget = LoanEquipmentItemView(requestDetails: widget.list[index]);
break;
case 13:
return AssetDeliveryItemView(requestDetails: list[index]);
itemWidget = AssetDeliveryItemView(requestDetails: widget.list[index]);
break;
case 14:
return MedicalGasInspectionItemView(requestDetails: list[index]);
itemWidget = MedicalGasInspectionItemView(requestDetails: widget.list[index]);
break;
case 15:
return DemoRequestItemView(requestDetails: list[index]);
itemWidget = DemoRequestItemView(requestDetails: widget.list[index]);
break;
case 16:
return IncidentItemView(requestDetails: list[index]);
itemWidget = IncidentItemView(requestDetails: widget.list[index]);
break;
default:
Container(
itemWidget = Container(
height: 100,
width: double.infinity,
color: Colors.grey,
);
}
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: slideAnimation,
child: itemWidget,
),
);
},
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();
}
class _MyAssetsPageState extends State<MyAssetsPage> {
class _MyAssetsPageState extends State<MyAssetsPage> with SingleTickerProviderStateMixin {
late AssetProvider _devicesProvider;
late UserProvider _userProvider;
late SettingProvider _settingProvider;
@ -45,6 +45,7 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
DeviceSearch? _searchAsset;
bool showListView = true;
late AnimationController _animationController;
TextEditingController numberController = TextEditingController();
TextEditingController snController = TextEditingController();
@ -59,12 +60,18 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
}
@override
void dispose() {
numberController.dispose();
snController.dispose();
_animationController.dispose();
_devicesProvider.reset();
super.dispose();
}
@ -193,29 +200,96 @@ class _MyAssetsPageState extends State<MyAssetsPage> {
duration: const Duration(milliseconds: 500),
child: showListView
? ListView.separated(
key: const ValueKey('list_view'),
padding: const EdgeInsets.all(16),
itemCount: _searchableList.length,
separatorBuilder: (listContext, itemIndex) => 12.height,
itemBuilder: (listContext, itemIndex) {
return AssetItemListView(
device: _searchableList[itemIndex],
onPressed: (device) {
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device);
// Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id);
},
// Calculate staggered animation delay
final delay = itemIndex * 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: AssetItemListView(
device: _searchableList[itemIndex],
onPressed: (device) {
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device);
},
),
),
);
},
)
: GridView.builder(
key: const ValueKey('grid_view'),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 188 / 261, crossAxisSpacing: 12, mainAxisSpacing: 12),
itemCount: _searchableList.length,
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) {
return AssetItemGridView(
device: _searchableList[index],
onPressed: (device) {
widget.fromBottomBar ? Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: device.id) : Navigator.of(context).pop(device);
},
// 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,
),
),
);
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