handle categories in mobile and web .
This commit is contained in:
parent
5144f1c446
commit
552f962f4f
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '$categories/:id',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return BlocProvider<CategoryCubit>(
|
||||
create: (context) => CategoryCubit()..getCategory(id),
|
||||
child: CategoriesPage(
|
||||
id: id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
]),
|
||||
StatefulShellBranch(routes: [
|
||||
GoRoute(
|
||||
path: categories,
|
||||
redirect: (context, state) {
|
||||
if (Responsive(context).isDesktop()) {
|
||||
return initial;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,121 +76,18 @@ class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
|
|||
child: Material(
|
||||
elevation: 4,
|
||||
child: Container(
|
||||
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');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 600,
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 4)
|
||||
],
|
||||
),
|
||||
child: CategoriesScreen()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -194,17 +95,14 @@ class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _menuItem(String title) {
|
||||
return ListTile(
|
||||
title: Text(title),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.zero,
|
||||
hoverColor: Colors.transparent,
|
||||
onTap: () {},
|
||||
onHover: (value) {
|
||||
onHovered.value = value;
|
||||
|
|
|
|||
Loading…
Reference in New Issue