Completed the Enhancement CR

faiz_development
Faiz Hashmi 3 weeks ago
parent 0d1fa2b1d7
commit e5b29e859c

@ -15,6 +15,7 @@ import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/filters_list.dart'; import 'package:mc_common_app/widgets/common_widgets/filters_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -70,7 +71,7 @@ class AppointmentsFragment extends StatelessWidget {
} }
}, },
child: appointmentsVM.state == ViewState.busy child: appointmentsVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator()) ? const AppointmentsListShimmer(itemCount: 5)
: appointmentsVM.myFilteredAppointmentsForCustomers.isEmpty : appointmentsVM.myFilteredAppointmentsForCustomers.isEmpty
? AppState().getIsViewOnly ? AppState().getIsViewOnly
? LocaleKeys.loginToViewAppointments.tr().toText(fontSize: 16, color: MyColors.lightTextColor) ? LocaleKeys.loginToViewAppointments.tr().toText(fontSize: 16, color: MyColors.lightTextColor)

@ -14,6 +14,7 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/branch_details_card.dart'; import 'package:mc_common_app/widgets/common_widgets/branch_details_card.dart';
import 'package:mc_common_app/widgets/common_widgets/filters_list.dart'; import 'package:mc_common_app/widgets/common_widgets/filters_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class BranchesFragment extends StatelessWidget { class BranchesFragment extends StatelessWidget {
@ -66,9 +67,7 @@ class BranchesFragment extends StatelessWidget {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
child: appointmentsVM.state == ViewState.busy child: appointmentsVM.state == ViewState.busy
? const Center( ? const BranchesListShimmer(itemCount: 5)
child: CircularProgressIndicator(),
)
: appointmentsVM.nearbyBranches.isEmpty : appointmentsVM.nearbyBranches.isEmpty
? Center( ? Center(
child: LocaleKeys.no_branch.tr().toText( child: LocaleKeys.no_branch.tr().toText(

@ -4,7 +4,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/config/routes.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/models/general_models/widgets_models.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart';
@ -15,11 +18,111 @@ import 'package:mc_common_app/views/appointments/widgets/common_appointment_slid
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/view_all_widget.dart'; import 'package:mc_common_app/widgets/common_widgets/view_all_widget.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HomeFragment extends StatelessWidget { class HomeFragment extends StatefulWidget {
const HomeFragment({Key? key}) : super(key: key); const HomeFragment({Key? key}) : super(key: key);
@override
State<HomeFragment> createState() => _HomeFragmentState();
}
class _HomeFragmentState extends State<HomeFragment> {
bool _isSearchingServices = false;
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _toggleSearch() {
setState(() {
_isSearchingServices = !_isSearchingServices;
if (!_isSearchingServices) {
_searchController.clear();
_searchQuery = '';
}
});
}
List<FilterListModel> _filterServices(List<FilterListModel> services) {
if (_searchQuery.isEmpty) {
return services;
}
return services.where((service) {
return service.title.toLowerCase().contains(_searchQuery.toLowerCase());
}).toList();
}
Widget _buildServicesHeader() {
if (_isSearchingServices) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: Row(
children: [
Expanded(
child: Container(
height: 55,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!, width: 1),
),
child: TextField(
controller: _searchController,
autofocus: true,
style: const TextStyle(fontSize: 16),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
decoration: InputDecoration(
hintText: LocaleKeys.searchService.tr(),
hintStyle: TextStyle(fontSize: 16, color: Colors.grey[600]),
prefixIcon: Icon(Icons.search, size: 26, color: Colors.grey[700]),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
),
),
),
),
14.width,
InkWell(
onTap: _toggleSearch,
child: Container(
height: 55,
width: 55,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!, width: 1),
),
child: Icon(Icons.close, size: 26, color: Colors.grey[700]),
),
),
],
),
);
} else {
return Row(
children: [
Expanded(
child: LocaleKeys.services.tr().toUpperCase().toText(fontSize: 20, isUpperCase: true, fontWeight: FontWeight.w600),
),
InkWell(
onTap: _toggleSearch,
child: const Icon(Icons.search, size: 28, color: MyColors.primaryColor),
),
],
).horPaddingMain();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -69,7 +172,14 @@ class HomeFragment extends StatelessWidget {
).horPaddingMain().toViewOnly(context, onTap: () { ).horPaddingMain().toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}), }),
CommonAppointmentSliderWidget() Consumer<AppointmentsVM>(
builder: (BuildContext context, AppointmentsVM appointmentVM, Widget? child) {
// Show shimmer when loading
if (appointmentVM.state == ViewState.busy) {
return const AppointmentSliderShimmer();
}
// Show actual data when idle
return CommonAppointmentSliderWidget()
.toWhiteContainer( .toWhiteContainer(
width: double.infinity, width: double.infinity,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -77,30 +187,49 @@ class HomeFragment extends StatelessWidget {
.margin(left: 21, right: 21) .margin(left: 21, right: 21)
.toViewOnly(context, onTap: () { .toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}), });
},
),
// SERVICES SECTION // SERVICES SECTION
14.height, 14.height,
Consumer( Consumer(
builder: (BuildContext context, AppointmentsVM appointmentVM, Widget? child) { builder: (BuildContext context, AppointmentsVM appointmentVM, Widget? child) {
// Show shimmer when loading
if (appointmentVM.state == ViewState.busy) { if (appointmentVM.state == ViewState.busy) {
return Center(child: CircularProgressIndicator()); return Column(
children: [
_buildServicesHeader(),
14.height,
const ServicesGridShimmer(),
],
);
} }
// Show actual data when idle
if (appointmentVM.branchesCategoriesFilterOptions.isNotEmpty) { if (appointmentVM.branchesCategoriesFilterOptions.isNotEmpty) {
// Filter services based on search query
final filteredServices = _filterServices(appointmentVM.branchesCategoriesFilterOptions);
return Column( return Column(
children: [ children: [
ViewAllWidget( _buildServicesHeader().toViewOnly(context, onTap: () {
title: LocaleKeys.services.tr().toUpperCase(),
subTitle: '',
onSubtitleTapped: () {},
).horPaddingMain().toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}), }),
MyServicesCategoriesWidget(homeCategories: appointmentVM.branchesCategoriesFilterOptions).horPaddingMain() 14.height,
filteredServices.isEmpty
? Padding(
padding: const EdgeInsets.all(40),
child: LocaleKeys.noServiceFound.tr().toText(
fontSize: 16,
color: Colors.grey,
textAlign: TextAlign.center,
),
)
: MyServicesCategoriesWidget(homeCategories: filteredServices).horPaddingMain(),
], ],
); );
} }
return SizedBox(); return const SizedBox();
}, },
), ),
@ -150,9 +279,22 @@ class HomeFragment extends StatelessWidget {
// ADS SECTION // ADS SECTION
Consumer( Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) { builder: (BuildContext context, AdVM adVM, Widget? child) {
// Show shimmer when loading
if (adVM.state == ViewState.busy) { if (adVM.state == ViewState.busy) {
return Center(child: CircularProgressIndicator()); return Column(
children: [
15.height,
ViewAllWidget(
title: LocaleKeys.my_active_Ads.tr().toUpperCase(),
subTitle: LocaleKeys.view_all.tr(),
onSubtitleTapped: () {},
).horPaddingMain(),
14.height,
const AdsListShimmer(itemCount: 2),
],
);
} }
// Show actual data when idle
return Column( return Column(
children: [ children: [
if (adVM.myActiveAdsForHome.isNotEmpty) ...[ if (adVM.myActiveAdsForHome.isNotEmpty) ...[
@ -173,7 +315,7 @@ class HomeFragment extends StatelessWidget {
shouldShowAdStatus: true, shouldShowAdStatus: true,
isAdsFragment: false, isAdsFragment: false,
adsList: adVM.myActiveAdsForHome, adsList: adVM.myActiveAdsForHome,
scrollPhysics: NeverScrollableScrollPhysics(), scrollPhysics: const NeverScrollableScrollPhysics(),
hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds,
).toViewOnly(context, onTap: () { ).toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
@ -181,30 +323,31 @@ class HomeFragment extends StatelessWidget {
], ],
) )
], ],
if (adVM.exploreAds.isNotEmpty) ...[ // RECOMMENDED ADS SECTION - COMMENTED OUT
Column( // if (adVM.exploreAds.isNotEmpty) ...[
children: [ // Column(
15.height, // children: [
ViewAllWidget( // 15.height,
title: LocaleKeys.myRecommendedAds.tr().toUpperCase(), // ViewAllWidget(
subTitle: LocaleKeys.view_all.tr(), // title: LocaleKeys.myRecommendedAds.tr().toUpperCase(),
onSubtitleTapped: () { // subTitle: LocaleKeys.view_all.tr(),
context.read<DashboardVmCustomer>().onNavbarTapped(3); // onSubtitleTapped: () {
context.read<AdVM>().updateIsExploreAds(true); // context.read<DashboardVmCustomer>().onNavbarTapped(3);
context.read<AdVM>().applyFilterOnExploreAds(vehicleBrandId: 0); // context.read<AdVM>().updateIsExploreAds(true);
}, // context.read<AdVM>().applyFilterOnExploreAds(vehicleBrandId: 0);
).horPaddingMain(), // },
AdsListWidget( // ).horPaddingMain(),
isDraftAds: false, // AdsListWidget(
shouldShowAdStatus: false, // isDraftAds: false,
adsList: adVM.exploreAds.length >= 3 ? adVM.exploreAds.take(3).toList() : adVM.exploreAds, // shouldShowAdStatus: false,
isAdsFragment: false, // adsList: adVM.exploreAds.length >= 3 ? adVM.exploreAds.take(3).toList() : adVM.exploreAds,
scrollPhysics: NeverScrollableScrollPhysics(), // isAdsFragment: false,
hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds, // scrollPhysics: NeverScrollableScrollPhysics(),
), // hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds,
], // ),
) // ],
] // )
// ]
], ],
); );
}, },

@ -1,7 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/view_models/appointments_view_model.dart'; import 'package:mc_common_app/view_models/appointments_view_model.dart';
import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart'; import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
@ -14,45 +17,89 @@ class MyServicesCategoriesWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( final isArabic = AppState().isArabic(context);
width: double.infinity, // Filter out categories with id == 0
height: 140, final filteredCategories = homeCategories.where((category) => category.id != 0).toList();
child: ListView.builder(
itemCount: homeCategories.length, // Define background colors to cycle through
scrollDirection: Axis.horizontal, final List<Color> bgColors = [
const Color(0xFFF3F4F6), // Light gray
const Color(0xFFFEF4E9), // Light cream/orange
const Color(0xFFE5EEE5), // Light green
const Color(0xFFF0EFF5), // Light purple
];
return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.9,
),
itemCount: filteredCategories.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
FilterListModel category = homeCategories[index]; FilterListModel category = filteredCategories[index];
if (category.id == 0) { // Cycle through colors based on index
return SizedBox(); Color bgColor = bgColors[index % bgColors.length];
}
return SizedBox( return Container(
width: 90, padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: bgColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
category.iconUrl.buildNetworkImage(height: 73, width: 73, fit: BoxFit.cover).toCircle(borderRadius: 100), category.isCustom ? category.iconUrl.buildNetworkImage(height: 32, width: 32, fit: BoxFit.fill) : category.iconUrl.buildNetworkImage(height: 32, width: 32, fit: BoxFit.fill),
9.height, 10.height,
Flexible( Flexible(
child: category.title.toText( child: (isArabic ? category.titleN : category.title).toText(
fontSize: 14, fontSize: 16,
textAlign: TextAlign.center, fontWeight: FontWeight.w600,
maxLines: 2, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
letterSpacing: -0.84, height: 1.2,
height: 18 / 14, ),
),
if (category.description.isNotEmpty || category.descriptionN.isNotEmpty) ...[
6.height,
Flexible(
child: (category.description).toText(
fontSize: 10,
fontWeight: FontWeight.w600,
maxLines: 3,
overflow: TextOverflow.ellipsis,
height: 1.2,
), ),
), ),
], ],
10.height,
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [MyAssets.arrowRight.buildSvg()],
),
],
), ),
).onPress(() { ).onPress(() {
final appointmentVM = context.read<AppointmentsVM>(); final appointmentVM = context.read<AppointmentsVM>();
final dashboardCustomerVM = context.read<DashboardVmCustomer>(); final dashboardCustomerVM = context.read<DashboardVmCustomer>();
context.read<DashboardVmCustomer>().onNavbarTapped(0); context.read<DashboardVmCustomer>().onNavbarTapped(0);
dashboardCustomerVM.onNavbarTapped(0); dashboardCustomerVM.onNavbarTapped(0);
appointmentVM.applyFilterOnBranches(index: index); // Use the original index from homeCategories for filtering
int originalIndex = homeCategories.indexOf(category);
appointmentVM.applyFilterOnBranches(index: originalIndex);
}); });
}, },
),
); );
} }
} }

Loading…
Cancel
Save