handle categories in mobile and web .

This commit is contained in:
OkaykOrhmn 2025-02-20 12:17:07 +03:30
parent 5144f1c446
commit 552f962f4f
12 changed files with 531 additions and 412 deletions

View File

@ -1,14 +1,19 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_children_cubit.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_cubit.dart';
import 'package:proxibuy/presentation/providers/category/cubit/category_cubit.dart';
import 'package:proxibuy/presentation/providers/user_info_cubit.dart';
import 'package:proxibuy/presentation/ui/screens/auth/auth_page.dart';
import 'package:proxibuy/presentation/ui/screens/categories/categories_page.dart';
import 'package:proxibuy/presentation/ui/screens/home/explore_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/screens/categories_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/screens/explore_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/home_page.dart';
import 'package:proxibuy/presentation/ui/screens/home/home_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/setting_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/screens/home_screen.dart';
import 'package:proxibuy/presentation/ui/screens/home/screens/setting_screen.dart';
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
import 'package:proxibuy/presentation/ui/widgets/navigations/drop_down_demo2.dart';
class AppRouter {
@ -18,7 +23,7 @@ class AppRouter {
static final product = '/product';
static final categories = '/categories';
static List<String> home = [initial, explore, setting];
static List<String> home = [initial, explore, setting, categories];
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
@ -85,20 +90,49 @@ class AppRouter {
return DropDownDemo3();
},
),
]),
]),
StatefulShellBranch(routes: [
GoRoute(
path: '$categories/:id',
path: categories,
redirect: (context, state) {
if (Responsive(context).isDesktop()) {
return initial;
}
return null;
},
builder: (BuildContext context, GoRouterState state) {
final id = state.pathParameters['id']!;
return BlocProvider<CategoryCubit>(
create: (context) => CategoryCubit()..getCategory(id),
child: CategoriesPage(
id: id,
),
StreamSubscription? categoriesSubscription;
String? id = state.uri.queryParameters['id'];
if (id != null) {
catId.value = id;
context.read<CategoriesChildrenCubit>().resetPagination();
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(id);
} else {
categoriesSubscription =
context.read<CategoriesCubit>().stream.listen(
(state) {
if (state is CategoriesLoaded) {
if (state.categories.isNotEmpty) {
id = state.categories.first.id;
catId.value = id;
// ignore: use_build_context_synchronously
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(id ?? '');
categoriesSubscription?.cancel();
}
}
},
);
}
return CategoriesScreen();
},
),
]),
]),
StatefulShellBranch(routes: [
GoRoute(
path: explore,

View File

@ -28,7 +28,7 @@ class ApiService {
enabled: kDebugMode,
));
setAuthToken(
'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJheC1zX3N3Tm5fU1hDTVkzWFowSDVKekNhQ0psVXh6bmZ0WHBxSk1YUEF3In0.eyJleHAiOjE3Mzk5NzkwNDUsImlhdCI6MTczOTk0MzA0NSwianRpIjoiMzY2NDllZWQtNTBiNy00ZWYyLWIyNmUtOTcxZDUwODBhODc1IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5saWFyYS5ydW4vcmVhbG1zL2xiYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmMzljODIxNi0zODhhLTQ0ZTEtODVhOC00Zjk5NmU2NmU2MDQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmcm9udGVuZCIsInNpZCI6IjcwZDlhNDhmLTcxMDktNGYxNi1hNTQ5LTVmNjE3MDk4ZTJmMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sYmEtYXBpLmxpYXJhLnJ1bi8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiLCIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYmEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19LCJmcm9udGVuZCI6eyJyb2xlcyI6WyJzaG9wIiwidXNlciJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImRlbW8gZGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJnaXZlbl9uYW1lIjoiZGVtbyIsImZhbWlseV9uYW1lIjoiZGVtbyIsImVtYWlsIjoiZGVtb0BnbWFpbC5jb20ifQ.H3MssiP94FiWyc7FqfoSK7Zt58VOZmunM1D-wrseDiTQF2lFIfCMYbWkKv1ko8hn-zn-1ExV_6auFCNS0C_wTyFbGq_IHwYQr9nGqji_cr4dum19doASpfZRQiR_oR5RM96Ht5lV3nY_X7o-ksJEiRDOHUZ-xmDLkxhGWfeTO90DXYWv_S39mS55R7SsQz7PI83B7ya9qgp-5GND_oY3iNjDYVTI46EQuGOTiNyLgUrRk64IFy4Bbhp-EVj7QhGwkDEOosAytzE5aqW98-1GUSfUS77P36Ln0olQEb_uYed8EDkdauAIPN-iN8Eg4q7QmT-fpBCP61dcy04FRfTzPw');
'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJheC1zX3N3Tm5fU1hDTVkzWFowSDVKekNhQ0psVXh6bmZ0WHBxSk1YUEF3In0.eyJleHAiOjE3NDAwNjQ5NDcsImlhdCI6MTc0MDAyODk0NywianRpIjoiNDVhYTQ0YWItYTEyMC00N2M3LWIyMWUtNjExNDllMWZlM2EyIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5saWFyYS5ydW4vcmVhbG1zL2xiYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmMzljODIxNi0zODhhLTQ0ZTEtODVhOC00Zjk5NmU2NmU2MDQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmcm9udGVuZCIsInNpZCI6ImEwNjUwMmU3LTY4MzItNDVhNC04MmUwLTdhZWI4ODBlZTc5ZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sYmEtYXBpLmxpYXJhLnJ1bi8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiLCIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYmEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19LCJmcm9udGVuZCI6eyJyb2xlcyI6WyJzaG9wIiwidXNlciJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImRlbW8gZGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJnaXZlbl9uYW1lIjoiZGVtbyIsImZhbWlseV9uYW1lIjoiZGVtbyIsImVtYWlsIjoiZGVtb0BnbWFpbC5jb20ifQ.iwAMzwyuQu9BkNXA65u8XfZIYoGtGvQ99qpCwTCPLDfe2DFZU_1wcOu5TV_7My80UHifzLR587CPGH1xwlJmD1JFtrEh0wk_sBe0iYKOmiz7OhFCLUNAuLkV5dvwYPfqyzPFYQPe8F0QdnK9107pF-15uyjn8w1MLfSE4uCRtc8Arh43WeZqoYALlLkHvQgltxvuBE_FXt_AulIBzmrhwDhA_Aw7gS4J05mGAkMAAX1B4mXxB-OCBN-ZPFEAU14YPQPHAjUdKtJ1scC4VYworjaEA3kkx_gYWdfi-o_3YykDlaE6fxOm4yQnFzg2DjJhE_7i8Lsjchc3GrBNzt4UeQ');
}
/// 🔹 Handle GET requests

View File

@ -2,7 +2,8 @@ import 'package:proxibuy/core/gen/assets.gen.dart';
class ScreenModel {
final String title;
final String route;
final SvgGenImage icon;
ScreenModel({required this.title, required this.icon});
ScreenModel({required this.title, required this.icon, required this.route});
}

View File

@ -22,7 +22,8 @@ void main() async {
BlocProvider<ThemModeCubit>(create: (context) => ThemModeCubit()),
BlocProvider<CategoriesChildrenCubit>(
create: (context) => CategoriesChildrenCubit()),
BlocProvider<CategoriesCubit>(create: (context) => CategoriesCubit()),
BlocProvider<CategoriesCubit>(
create: (context) => CategoriesCubit()..getAllCategories()),
BlocProvider<UserInfoCubit>(
create: (context) => UserInfoCubit()..getUserInfo()),
],

View File

@ -1,124 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_children_cubit.dart';
import 'package:proxibuy/presentation/providers/category/cubit/category_cubit.dart';
import 'package:proxibuy/presentation/ui/widgets/default_placeholder.dart';
class CategoriesPage extends StatefulWidget {
final String id;
const CategoriesPage({super.key, required this.id});
@override
State<CategoriesPage> createState() => _CategoriesPageState();
}
class _CategoriesPageState extends State<CategoriesPage> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
WidgetsBinding.instance.addPostFrameCallback((_) async {
await context.read<CategoriesChildrenCubit>().resetPagination();
if (mounted) {
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(widget.id);
}
});
}
void _onScroll() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
context.read<CategoriesChildrenCubit>().getAllChildCategories(widget.id);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: BlocBuilder<CategoryCubit, CategoryState>(
builder: (context, state) {
if (state is CategorySuccess) {
return Text(state.category.name ?? '...');
}
return SizedBox();
},
)),
body: BlocBuilder<CategoriesChildrenCubit, CategoriesChildrenState>(
builder: (context, state) {
if (state is CategoriesChildrenLoaded) {
if (state.categories.isEmpty) {
return Center(child: Text('Empty'));
}
return SingleChildScrollView(
child: Column(
children: [
ListView.builder(
// controller: _scrollController,
itemCount: state.categories.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
if (index >= state.categories.length) {
return Center(child: CircularProgressIndicator());
}
final category = state.categories[index];
return ListTile(
leading: SvgPicture.network(
'${state.categories[index].url}',
color: Theme.of(context).colorScheme.onSurface,
placeholderBuilder: (context) => DefaultPlaceHolder(
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.white),
)),
errorBuilder: (context, error, stackTrace) =>
Icon(Icons.category_outlined),
),
title: Text(category.name ?? ''),
);
},
),
if (state.isLoading) LinearProgressIndicator()
],
),
);
} else if (state is CategoriesChildrenError) {
return Center(child: Text(state.errorMessage!));
} else {
return ListView.builder(
// controller: _scrollController,
itemCount: 20,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return DefaultPlaceHolder(
child: Container(
margin: EdgeInsets.symmetric(vertical: 8),
color: Colors.white,
child: ListTile(
leading: Icon(Icons.abc_outlined),
title: Text(''),
),
),
);
},
);
}
},
),
);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:proxibuy/core/gen/assets.gen.dart';
import 'package:proxibuy/core/routes/app_router.dart';
import 'package:proxibuy/core/utils/empty_space.dart';
import 'package:proxibuy/data/models/screen_model.dart';
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
import 'package:proxibuy/presentation/ui/theme/theme.dart';
import 'package:proxibuy/presentation/ui/widgets/navigations/categories_mega_menu.dart';
class HomeDeskPage extends StatefulWidget {
final Widget child;
const HomeDeskPage({super.key, required this.child});
@override
State<HomeDeskPage> createState() => _HomeDeskPageState();
}
class _HomeDeskPageState extends State<HomeDeskPage> {
int selectedIndex = 0;
List<ScreenModel> deskScreens = [
ScreenModel(
title: 'Home',
icon: Assets.icon.outline.home,
route: AppRouter.initial),
ScreenModel(
title: 'Explore',
icon: Assets.icon.outline.map,
route: AppRouter.explore),
ScreenModel(
title: 'Settings',
icon: Assets.icon.outline.setting,
route: AppRouter.setting),
];
void _onItemTapped(BuildContext context, int index) {
setState(() {
selectedIndex = index;
});
switch (index) {
case 0:
context.go(AppRouter.initial);
break;
case 1:
context.go(AppRouter.explore);
break;
case 2:
context.go(AppRouter.setting);
break;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
navBar(context),
Expanded(child: widget.child),
],
),
);
}
Padding navBar(BuildContext context) {
final defaultBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Theme.of(context).colorScheme.surface, width: 2));
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SelectableText(
"Proxibuy",
style: Theme.of(context).textTheme.displaySmall,
),
32.w,
Row(
children: [
...List.generate(
deskScreens.length,
(index) => Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: selectedIndex == index
? themeColor(context)
?.primaryLightSurface
.withAlpha(90)
: null),
child: IconButton(
splashRadius: 12,
onPressed: () {
_onItemTapped(context, index);
},
icon: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
deskScreens[index].icon.svg(
color: selectedIndex == index
? Theme.of(context).primaryColor
: Theme.of(context)
.colorScheme
.onSurface),
12.w,
Text(
deskScreens[index].title,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: selectedIndex == index
? Theme.of(context).primaryColor
: Theme.of(context)
.colorScheme
.onSurface),
)
],
)),
),
24.w
],
),
),
CategoriesMegaMenu()
],
),
],
),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
8.w,
Flexible(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
child: TextField(
decoration: InputDecoration(
hintText: 'what are you looking for?',
suffixIcon: Padding(
padding: const EdgeInsets.all(8.0),
child: Assets.icon.outline.search.svg(
color: Theme.of(context).colorScheme.onSurface,
width: 16,
height: 16),
),
enabledBorder: defaultBorder,
border: defaultBorder),
),
)),
32.w,
IconButton(
icon: Assets.icon.outline.notificationBing
.svg(color: Theme.of(context).colorScheme.onSurface),
onPressed: () {},
),
12.w,
IconButton(
icon: Icon(Icons.brightness_6),
onPressed: () {
context.read<ThemModeCubit>().changeTheme();
},
),
],
),
)
],
),
);
}
}

View File

@ -7,6 +7,7 @@ import 'package:proxibuy/core/routes/app_router.dart';
import 'package:proxibuy/core/utils/empty_space.dart';
import 'package:proxibuy/data/models/screen_model.dart';
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
import 'package:proxibuy/presentation/ui/screens/home/home_desk_page.dart';
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
import 'package:proxibuy/presentation/ui/theme/theme.dart';
import 'package:proxibuy/presentation/ui/widgets/navigations/categories_mega_menu.dart';
@ -21,24 +22,40 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage> {
int selectedIndex = 0;
@override
initState() {
super.initState();
}
List<ScreenModel> screens = [
ScreenModel(title: 'Home', icon: Assets.icon.outline.home),
ScreenModel(title: 'Explore', icon: Assets.icon.outline.map),
ScreenModel(title: 'Settings', icon: Assets.icon.outline.setting),
ScreenModel(
title: 'Home',
icon: Assets.icon.outline.home,
route: AppRouter.initial),
ScreenModel(
title: 'Categories',
icon: Assets.icon.outline.search,
route: AppRouter.categories),
ScreenModel(
title: 'Explore',
icon: Assets.icon.outline.map,
route: AppRouter.explore),
ScreenModel(
title: 'Settings',
icon: Assets.icon.outline.setting,
route: AppRouter.setting),
];
int _getSelectedIndex(BuildContext context) {
final g = GoRouterState.of(context);
final String location = g.fullPath.toString();
return screens.indexWhere((element) => element.route == location);
}
@override
Widget build(BuildContext context) {
return Responsive(context).builder(
desktop: Scaffold(
body: Column(
children: [
navBar(context),
Expanded(child: body()),
],
),
),
desktop: HomeDeskPage(child: body()),
mobile: Scaffold(
body: body(),
bottomNavigationBar: bottomNavigationBar(context),
@ -46,127 +63,14 @@ class _HomePageState extends State<HomePage> {
);
}
Padding navBar(BuildContext context) {
final defaultBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Theme.of(context).colorScheme.surface, width: 2));
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SelectableText(
"Proxibuy",
style: Theme.of(context).textTheme.displaySmall,
),
32.w,
Row(
children: [
...List.generate(
screens.length,
(index) => Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: selectedIndex == index
? themeColor(context)
?.primaryLightSurface
.withAlpha(90)
: null),
child: IconButton(
splashRadius: 12,
onPressed: () {
_onItemTapped(context, index);
},
icon: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
screens[index].icon.svg(
color: selectedIndex == index
? Theme.of(context).primaryColor
: Theme.of(context)
.colorScheme
.onSurface),
12.w,
Text(
screens[index].title,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: selectedIndex == index
? Theme.of(context).primaryColor
: Theme.of(context)
.colorScheme
.onSurface),
)
],
)),
),
24.w
],
),
),
CategoriesMegaMenu()
],
),
],
),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
8.w,
Flexible(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
child: TextField(
decoration: InputDecoration(
hintText: 'what are you looking for?',
suffixIcon: Padding(
padding: const EdgeInsets.all(8.0),
child: Assets.icon.outline.search.svg(
color: Theme.of(context).colorScheme.onSurface,
width: 16,
height: 16),
),
enabledBorder: defaultBorder,
border: defaultBorder),
),
)),
32.w,
IconButton(
icon: Assets.icon.outline.notificationBing
.svg(color: Theme.of(context).colorScheme.onSurface),
onPressed: () {},
),
12.w,
IconButton(
icon: Icon(Icons.brightness_6),
onPressed: () {
context.read<ThemModeCubit>().changeTheme();
},
),
],
),
)
],
),
);
}
Padding bottomNavigationBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: BottomNavyBar(
selectedIndex: selectedIndex,
selectedIndex: _getSelectedIndex(context),
// landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
// showUnselectedLabels: false,
onItemSelected: (index) => _onItemTapped(context, index),
onItemSelected: (index) => _onItemTapped(context, screens[index].route),
backgroundColor: Theme.of(context).colorScheme.surface,
itemCornerRadius: 12,
itemPadding: EdgeInsets.symmetric(horizontal: 16),
@ -179,7 +83,7 @@ class _HomePageState extends State<HomePage> {
screens.length,
(index) => BottomNavyBarItem(
icon: screens[index].icon.svg(
color: selectedIndex == index
color: _getSelectedIndex(context) == index
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.onSurface),
activeColor: Theme.of(context).primaryColor,
@ -195,20 +99,7 @@ class _HomePageState extends State<HomePage> {
return widget.child;
}
void _onItemTapped(BuildContext context, int index) {
setState(() {
selectedIndex = index;
});
switch (index) {
case 0:
context.go(AppRouter.initial);
break;
case 1:
context.go(AppRouter.explore);
break;
case 2:
context.go(AppRouter.setting);
break;
}
void _onItemTapped(BuildContext context, String route) {
context.go(route);
}
}

View File

@ -0,0 +1,239 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_children_cubit.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_cubit.dart';
import 'package:proxibuy/presentation/ui/widgets/default_placeholder.dart';
ValueNotifier<String?> catId = ValueNotifier(null);
class CategoriesScreen extends StatefulWidget {
const CategoriesScreen({super.key});
@override
State<CategoriesScreen> createState() => _CategoriesScreenState();
}
class _CategoriesScreenState extends State<CategoriesScreen> {
final ScrollController _scrollController = ScrollController();
StreamSubscription? _categoriesSubscription;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(catId.value ?? '');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: BlocBuilder<CategoriesCubit, CategoriesState>(
builder: (context, state) {
if (state is CategoriesLoaded) {
if (state.categories.isEmpty) {
return Center(child: Text('Empty'));
}
return SingleChildScrollView(
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ListView.builder(
// controller: _scrollController,
itemCount: state.categories.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
final category = state.categories[index];
return InkWell(
onTap: () {
context
.read<CategoriesChildrenCubit>()
.resetPagination();
setState(() {
catId.value = category.id;
});
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(catId.value ?? '');
},
child: ValueListenableBuilder(
valueListenable: catId,
builder: (context, selectedId, _) {
return Container(
color: selectedId == category.id
? Theme.of(context)
.scaffoldBackgroundColor
: Theme.of(context)
.colorScheme
.surface,
child: ListTile(
leading: SvgPicture.network(
'${category.url}',
color: Theme.of(context)
.colorScheme
.onSurface,
placeholderBuilder: (context) =>
DefaultPlaceHolder(
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white),
)),
errorBuilder: (context, error,
stackTrace) =>
Icon(Icons.category_outlined),
),
title: Text(category.name ?? ''),
),
);
}),
);
},
),
if (state.isLoading) LinearProgressIndicator()
],
),
),
);
} else if (state is CategoriesChildrenError) {
return Center(child: Text(state.errorMessage!));
} else {
return ListView.builder(
// controller: _scrollController,
itemCount: 20,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return DefaultPlaceHolder(
child: Container(
margin: EdgeInsets.symmetric(vertical: 8),
color: Colors.white,
child: ListTile(
leading: Icon(Icons.abc_outlined),
title: Text(''),
),
),
);
},
);
}
},
),
),
VerticalDivider(),
Expanded(
flex: 2,
child:
BlocBuilder<CategoriesChildrenCubit, CategoriesChildrenState>(
builder: (context, state) {
if (state is CategoriesChildrenLoaded) {
if (state.categories.isEmpty) {
return Center(child: Text('Empty'));
}
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextButton(
onPressed: () {},
child: Text(
'See all',
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context).primaryColor),
)),
),
ListView.builder(
// controller: _scrollController,
itemCount: state.categories.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
if (index >= state.categories.length) {
return Center(child: CircularProgressIndicator());
}
final category = state.categories[index];
return ListTile(
leading: SvgPicture.network(
'${state.categories[index].url}',
color: Theme.of(context).colorScheme.onSurface,
placeholderBuilder: (context) =>
DefaultPlaceHolder(
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white),
)),
errorBuilder: (context, error, stackTrace) =>
Icon(Icons.category_outlined),
),
title: Text(category.name ?? ''),
);
},
),
if (state.isLoading) LinearProgressIndicator()
],
),
);
} else if (state is CategoriesChildrenError) {
return Center(child: Text(state.errorMessage!));
} else {
return ListView.builder(
// controller: _scrollController,
itemCount: 20,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
return DefaultPlaceHolder(
child: Container(
margin: EdgeInsets.symmetric(vertical: 8),
color: Colors.white,
child: ListTile(
leading: Icon(Icons.abc_outlined),
title: Text(''),
),
),
);
},
);
}
},
),
),
],
),
);
}
@override
void dispose() {
_scrollController.dispose();
_categoriesSubscription?.cancel();
super.dispose();
}
}

View File

@ -26,9 +26,9 @@ class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<CategoriesCubit>().getAllCategories();
});
// WidgetsBinding.instance.addPostFrameCallback((_) {
// context.read<CategoriesCubit>().getAllCategories();
// });
}
@override
@ -590,6 +590,7 @@ class _HomeScreenState extends State<HomeScreen> {
Column categories(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
titleDivider(context, title: 'what\'s on your mind?', top: 16),
BlocBuilder<CategoriesCubit, CategoriesState>(
@ -615,7 +616,7 @@ class _HomeScreenState extends State<HomeScreen> {
child: InkWell(
onTap: () {
context.go(
'${AppRouter.categories}/${state.categories[index].id}');
'${AppRouter.categories}?id=${state.categories[index].id}');
},
onHover: (value) {
isHovered.value = value;

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:proxibuy/core/utils/empty_space.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_children_cubit.dart';
import 'package:proxibuy/presentation/providers/category/cubit/categories_cubit.dart';
import 'package:proxibuy/presentation/ui/screens/home/screens/categories_screen.dart';
import 'package:proxibuy/presentation/ui/theme/colors.dart';
import 'package:proxibuy/presentation/ui/theme/theme.dart';
import 'package:proxibuy/presentation/ui/widgets/default_placeholder.dart';
class CategoriesMegaMenu extends StatefulWidget {
const CategoriesMegaMenu({super.key});
@ -61,6 +64,7 @@ class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
}
OverlayEntry _createMegaMenu() {
catId.value ??= context.read<CategoriesCubit>().state.categories.first.id;
return OverlayEntry(
builder: (context) => Positioned(
width: 800, // Adjust width of the mega menu
@ -72,131 +76,22 @@ class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
child: Material(
elevation: 4,
child: Container(
constraints: BoxConstraints(
maxHeight: 600,
),
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.surface,
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
),
child: Row(
children: [
Expanded(
child: BlocBuilder<CategoriesCubit, CategoriesState>(
builder: (context, state) {
if (state.isLoading && state.categories.isEmpty) {
return Center(child: CircularProgressIndicator());
} else if (state.errorMessage != null) {
return Center(child: Text(state.errorMessage!));
} else {
return SingleChildScrollView(
child: ValueListenableBuilder(
valueListenable: selectedId,
builder: (context, id, _) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
...List.generate(
context
.read<CategoriesCubit>()
.state
.categories
.length,
(index) {
final cat = context
.read<CategoriesCubit>()
.state
.categories[index];
return InkWell(
onTap: () {
if (cat.id != null) {
selectedId.value = cat.id!;
context
.read<
CategoriesChildrenCubit>()
.resetPagination();
context
.read<
CategoriesChildrenCubit>()
.getAllChildCategories(
id!);
}
},
child: Container(
color: cat.id == id
? semanticBlue
: null,
child: _menuItem(
cat.name ?? '')));
},
)
],
);
}),
);
}
},
),
),
Expanded(
child: BlocBuilder<CategoriesChildrenCubit,
CategoriesChildrenState>(
builder: (context, state) {
if (state.isLoading && state.categories.isEmpty) {
return Center(child: CircularProgressIndicator());
} else if (state.errorMessage != null) {
return Center(child: Text(state.errorMessage!));
} else {
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification.metrics.pixels ==
scrollNotification
.metrics.maxScrollExtent) {
context
.read<CategoriesChildrenCubit>()
.getAllChildCategories(selectedId.value!);
}
return false;
},
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 300), // Adjust height as needed
child: ListView.builder(
shrinkWrap: true,
itemCount: state.categories.length +
(state.isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= state.categories.length) {
return Center(
child: CircularProgressIndicator());
}
final category = state.categories[index];
return ListTile(
title: Text(category.name ?? ''),
onTap: () {
print('${category.name} clicked');
},
);
},
),
),
);
}
},
),
)
color: Theme.of(context).scaffoldBackgroundColor,
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 4)
],
),
child: CategoriesScreen()),
),
),
),
),
),
);
}
Widget _menuItem(String title) {
return ListTile(
title: Text(title),
);
}
@ -205,6 +100,9 @@ class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
return CompositedTransformTarget(
link: _layerLink,
child: InkWell(
splashColor: Colors.transparent,
borderRadius: BorderRadius.zero,
hoverColor: Colors.transparent,
onTap: () {},
onHover: (value) {
onHovered.value = value;