categories handle
This commit is contained in:
parent
5b993e5c4c
commit
5144f1c446
|
|
@ -10,6 +10,7 @@
|
||||||
analyzer:
|
analyzer:
|
||||||
errors:
|
errors:
|
||||||
avoid_print: ignore
|
avoid_print: ignore
|
||||||
|
deprecated_member_use: ignore
|
||||||
deprecated_member_use_from_same_package: ignore
|
deprecated_member_use_from_same_package: ignore
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// This file is no longer needed and can be deleted.
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/user_info_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/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/explore_screen.dart';
|
||||||
import 'package:proxibuy/presentation/ui/screens/home/home_page.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/home_screen.dart';
|
||||||
import 'package:proxibuy/presentation/ui/screens/home/setting_screen.dart';
|
import 'package:proxibuy/presentation/ui/screens/home/setting_screen.dart';
|
||||||
import 'package:proxibuy/presentation/ui/screens/product/product_page.dart';
|
import 'package:proxibuy/presentation/ui/widgets/navigations/drop_down_demo2.dart';
|
||||||
|
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
static final initial = '/';
|
static final initial = '/';
|
||||||
static final explore = '/explore';
|
static final explore = '/explore';
|
||||||
static final setting = '/settings';
|
static final setting = '/settings';
|
||||||
static final product = '/product';
|
static final product = '/product';
|
||||||
|
static final categories = '/categories';
|
||||||
|
|
||||||
static List<String> home = [initial, explore, setting];
|
static List<String> home = [initial, explore, setting];
|
||||||
|
|
||||||
|
|
@ -79,9 +82,20 @@ class AppRouter {
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: product,
|
path: product,
|
||||||
builder: (BuildContext context, GoRouterState state) {
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
return ProductPage();
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
routes: <RouteBase>[],
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ class ApiService {
|
||||||
late Dio _dio;
|
late Dio _dio;
|
||||||
|
|
||||||
void setAuthToken(String token) {
|
void setAuthToken(String token) {
|
||||||
_dio.options.headers['Authorization'] =
|
_dio.options.headers['Authorization'] = 'Bearer $token';
|
||||||
'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJheC1zX3N3Tm5fU1hDTVkzWFowSDVKekNhQ0psVXh6bmZ0WHBxSk1YUEF3In0.eyJleHAiOjE3Mzk4ODcwNzksImlhdCI6MTczOTg4NTI3OSwianRpIjoiYTlmZGE4NzItZmJhZC00ZmQ5LTg3MTMtMTcwYjM3MWE1NTM2IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5saWFyYS5ydW4vcmVhbG1zL2xiYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmMzljODIxNi0zODhhLTQ0ZTEtODVhOC00Zjk5NmU2NmU2MDQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmcm9udGVuZCIsInNpZCI6IjIwNWNmNTBkLTE5MWUtNGViMS1iODBkLTMzMTFiNjIzYTZhMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sYmEtYXBpLmxpYXJhLnJ1bi8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiLCIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYmEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19LCJmcm9udGVuZCI6eyJyb2xlcyI6WyJzaG9wIiwidXNlciJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImRlbW8gZGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJnaXZlbl9uYW1lIjoiZGVtbyIsImZhbWlseV9uYW1lIjoiZGVtbyIsImVtYWlsIjoiZGVtb0BnbWFpbC5jb20ifQ.r_RtuJLZ9HQZdoy1Fi29d7hbXqcQ497XThKUmAjd_ClenE2VjpTzpogTOFwrrzJqRxm9fDpBflliqBWRg7KwR7irxW4HHBBaJmCaLzLbG8EtiuvciwDc9ugqLwUvGs2Gnzc7P0RA9aWzVY2lBkDZa7GeEvCCI4k0pbSWsAjX-Ax2vHxCqW8GzMsBeLkQ2BE9cyKX5Q3f-yne5HjDoxF1350qPlXwxIRkmM2Ct-aiMm7CjCaawPdTqdritsWejwTtaVCQtzkHPyeToPwE_X1YLWDlFdwpWSzjiI2fDFyV-MykLgDZevxHtvFoIg-2f6Zsm2_t7AMoCr-tM7vquCZTaw';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiService() {
|
ApiService() {
|
||||||
|
|
@ -28,7 +27,8 @@ class ApiService {
|
||||||
)..interceptors.add(PrettyDioLogger(
|
)..interceptors.add(PrettyDioLogger(
|
||||||
enabled: kDebugMode,
|
enabled: kDebugMode,
|
||||||
));
|
));
|
||||||
setAuthToken('');
|
setAuthToken(
|
||||||
|
'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJheC1zX3N3Tm5fU1hDTVkzWFowSDVKekNhQ0psVXh6bmZ0WHBxSk1YUEF3In0.eyJleHAiOjE3Mzk5NzkwNDUsImlhdCI6MTczOTk0MzA0NSwianRpIjoiMzY2NDllZWQtNTBiNy00ZWYyLWIyNmUtOTcxZDUwODBhODc1IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5saWFyYS5ydW4vcmVhbG1zL2xiYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmMzljODIxNi0zODhhLTQ0ZTEtODVhOC00Zjk5NmU2NmU2MDQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmcm9udGVuZCIsInNpZCI6IjcwZDlhNDhmLTcxMDktNGYxNi1hNTQ5LTVmNjE3MDk4ZTJmMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sYmEtYXBpLmxpYXJhLnJ1bi8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiLCIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYmEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19LCJmcm9udGVuZCI6eyJyb2xlcyI6WyJzaG9wIiwidXNlciJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImRlbW8gZGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJnaXZlbl9uYW1lIjoiZGVtbyIsImZhbWlseV9uYW1lIjoiZGVtbyIsImVtYWlsIjoiZGVtb0BnbWFpbC5jb20ifQ.H3MssiP94FiWyc7FqfoSK7Zt58VOZmunM1D-wrseDiTQF2lFIfCMYbWkKv1ko8hn-zn-1ExV_6auFCNS0C_wTyFbGq_IHwYQr9nGqji_cr4dum19doASpfZRQiR_oR5RM96Ht5lV3nY_X7o-ksJEiRDOHUZ-xmDLkxhGWfeTO90DXYWv_S39mS55R7SsQz7PI83B7ya9qgp-5GND_oY3iNjDYVTI46EQuGOTiNyLgUrRk64IFy4Bbhp-EVj7QhGwkDEOosAytzE5aqW98-1GUSfUS77P36Ln0olQEb_uYed8EDkdauAIPN-iN8Eg4q7QmT-fpBCP61dcy04FRfTzPw');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🔹 Handle GET requests
|
/// 🔹 Handle GET requests
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,9 @@ class Meta {
|
||||||
currentPage = json['currentPage'];
|
currentPage = json['currentPage'];
|
||||||
totalPages = json['totalPages'];
|
totalPages = json['totalPages'];
|
||||||
if (json['sortBy'] != null) {
|
if (json['sortBy'] != null) {
|
||||||
// sortBy = <List<String>>[];
|
sortBy = (json['sortBy'] as List)
|
||||||
// json['sortBy'].forEach((v) {
|
.map((item) => (item as List).map((e) => e.toString()).toList())
|
||||||
// sortBy!.addAll(v.toList());
|
.toList();
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,56 @@
|
||||||
class CategoriesModel {
|
class CategoriesModel {
|
||||||
String? id;
|
String? id;
|
||||||
|
String? createdAt;
|
||||||
|
String? updatedAt;
|
||||||
|
String? state;
|
||||||
String? name;
|
String? name;
|
||||||
int? count;
|
int? count;
|
||||||
|
String? description;
|
||||||
|
String? createdBy;
|
||||||
String? parentId;
|
String? parentId;
|
||||||
|
String? icon;
|
||||||
|
String? url;
|
||||||
|
|
||||||
CategoriesModel({this.id, this.name, this.count, this.parentId});
|
CategoriesModel(
|
||||||
|
{this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.state,
|
||||||
|
this.name,
|
||||||
|
this.count,
|
||||||
|
this.description,
|
||||||
|
this.createdBy,
|
||||||
|
this.parentId,
|
||||||
|
this.icon,
|
||||||
|
this.url});
|
||||||
|
|
||||||
CategoriesModel.fromJson(Map<String, dynamic> json) {
|
CategoriesModel.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
|
createdAt = json['createdAt'];
|
||||||
|
updatedAt = json['updatedAt'];
|
||||||
|
state = json['state'];
|
||||||
name = json['name'];
|
name = json['name'];
|
||||||
count = json['count'];
|
count = json['count'];
|
||||||
|
description = json['description'];
|
||||||
|
createdBy = json['created_by'];
|
||||||
parentId = json['parent_id'];
|
parentId = json['parent_id'];
|
||||||
|
icon = json['icon'];
|
||||||
|
url = json['url'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
data['id'] = id;
|
data['id'] = id;
|
||||||
|
data['createdAt'] = createdAt;
|
||||||
|
data['updatedAt'] = updatedAt;
|
||||||
|
data['state'] = state;
|
||||||
data['name'] = name;
|
data['name'] = name;
|
||||||
data['count'] = count;
|
data['count'] = count;
|
||||||
|
data['description'] = description;
|
||||||
|
data['created_by'] = createdBy;
|
||||||
data['parent_id'] = parentId;
|
data['parent_id'] = parentId;
|
||||||
|
data['icon'] = icon;
|
||||||
|
data['url'] = url;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import 'package:proxibuy/core/services/api/response_model.dart';
|
||||||
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||||
|
|
||||||
class CategoryRepository {
|
class CategoryRepository {
|
||||||
static Future<List<CategoriesModel>?> fetchAll() async {
|
static Future<ResponseModel<List<CategoriesModel>>> fetchAll(
|
||||||
|
{int page = 1}) async {
|
||||||
try {
|
try {
|
||||||
var response = await apiService.get(ApiRoutes.category);
|
var response = await apiService.get('${ApiRoutes.category}?page=$page');
|
||||||
print("Users: $response");
|
|
||||||
final res = ResponseModel<List<CategoriesModel>>.fromJson(
|
final res = ResponseModel<List<CategoriesModel>>.fromJson(
|
||||||
response,
|
response,
|
||||||
(data) {
|
(data) {
|
||||||
|
|
@ -16,7 +16,37 @@ class CategoryRepository {
|
||||||
.toList();
|
.toList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return res.data;
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
print("Error: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<ResponseModel<List<CategoriesModel>>> fetchAllChild(
|
||||||
|
{int page = 1, required final String id}) async {
|
||||||
|
try {
|
||||||
|
var response = await apiService
|
||||||
|
.get('${ApiRoutes.category}?page=$page?filter.parent_id=$id');
|
||||||
|
final res = ResponseModel<List<CategoriesModel>>.fromJson(
|
||||||
|
response,
|
||||||
|
(data) {
|
||||||
|
return (data as List)
|
||||||
|
.map((item) => CategoriesModel.fromJson(item))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
print("Error: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<CategoriesModel> fetchOne({required final String id}) async {
|
||||||
|
try {
|
||||||
|
var response = await apiService.get('${ApiRoutes.category}/$id');
|
||||||
|
return CategoriesModel.fromJson(response['category']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error: $e");
|
print("Error: $e");
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:proxibuy/core/routes/app_router.dart';
|
import 'package:proxibuy/core/routes/app_router.dart';
|
||||||
import 'package:proxibuy/core/utils/my_custom_scroll_behavior.dart';
|
import 'package:proxibuy/core/utils/my_custom_scroll_behavior.dart';
|
||||||
import 'package:proxibuy/data/storage/shared_preferences_helper.dart';
|
import 'package:proxibuy/data/storage/shared_preferences_helper.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/categories_cubit.dart';
|
import 'package:proxibuy/presentation/providers/category/cubit/categories_children_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/category/cubit/categories_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/user_info_cubit.dart';
|
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
|
||||||
|
import 'package:proxibuy/presentation/providers/user_info_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
||||||
import 'package:url_strategy/url_strategy.dart';
|
import 'package:url_strategy/url_strategy.dart';
|
||||||
|
|
||||||
|
|
@ -19,6 +20,8 @@ void main() async {
|
||||||
runApp(MultiBlocProvider(
|
runApp(MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ThemModeCubit>(create: (context) => ThemModeCubit()),
|
BlocProvider<ThemModeCubit>(create: (context) => ThemModeCubit()),
|
||||||
|
BlocProvider<CategoriesChildrenCubit>(
|
||||||
|
create: (context) => CategoriesChildrenCubit()),
|
||||||
BlocProvider<CategoriesCubit>(create: (context) => CategoriesCubit()),
|
BlocProvider<CategoriesCubit>(create: (context) => CategoriesCubit()),
|
||||||
BlocProvider<UserInfoCubit>(
|
BlocProvider<UserInfoCubit>(
|
||||||
create: (context) => UserInfoCubit()..getUserInfo()),
|
create: (context) => UserInfoCubit()..getUserInfo()),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||||
|
import 'package:proxibuy/data/repositories/category_repository.dart';
|
||||||
|
|
||||||
|
part 'categories_children_state.dart';
|
||||||
|
|
||||||
|
class CategoriesChildrenCubit extends Cubit<CategoriesChildrenState> {
|
||||||
|
CategoriesChildrenCubit() : super(CategoriesChildrenInitial());
|
||||||
|
|
||||||
|
void updateCategories(List<CategoriesModel> newCategories) {
|
||||||
|
emit(state.copyWith(categories: newCategories));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllChildCategories(String id) async {
|
||||||
|
if (state.isLoading || state.currentPage > state.totalPages) return;
|
||||||
|
if (state.currentPage == 1) {
|
||||||
|
emit(CategoriesChildrenLoading());
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final response = await CategoryRepository.fetchAllChild(
|
||||||
|
page: state.currentPage, id: id);
|
||||||
|
emit(CategoriesChildrenLoaded(
|
||||||
|
categories: List.from(state.categories)..addAll(response.data ?? []),
|
||||||
|
isLoading: false,
|
||||||
|
currentPage: state.currentPage + 1,
|
||||||
|
totalPages: response.meta?.totalPages ?? 1,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(CategoriesChildrenError(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future resetPagination() async {
|
||||||
|
emit(CategoriesChildrenInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
part of 'categories_children_cubit.dart';
|
||||||
|
|
||||||
|
class CategoriesChildrenState {
|
||||||
|
final List<CategoriesModel> categories;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? errorMessage;
|
||||||
|
final int currentPage;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
const CategoriesChildrenState({
|
||||||
|
this.categories = const [],
|
||||||
|
this.isLoading = false,
|
||||||
|
this.errorMessage,
|
||||||
|
this.currentPage = 1,
|
||||||
|
this.totalPages = 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
CategoriesChildrenState copyWith({
|
||||||
|
List<CategoriesModel>? categories,
|
||||||
|
bool? isLoading,
|
||||||
|
String? errorMessage,
|
||||||
|
int? currentPage,
|
||||||
|
int? totalPages,
|
||||||
|
}) {
|
||||||
|
return CategoriesChildrenState(
|
||||||
|
categories: categories ?? this.categories,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
currentPage: currentPage ?? this.currentPage,
|
||||||
|
totalPages: totalPages ?? this.totalPages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoriesChildrenInitial extends CategoriesChildrenState {
|
||||||
|
const CategoriesChildrenInitial()
|
||||||
|
: super(
|
||||||
|
categories: const [],
|
||||||
|
currentPage: 1,
|
||||||
|
errorMessage: null,
|
||||||
|
isLoading: false,
|
||||||
|
totalPages: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoriesChildrenLoading extends CategoriesChildrenState {}
|
||||||
|
|
||||||
|
final class CategoriesChildrenLoaded extends CategoriesChildrenState {
|
||||||
|
CategoriesChildrenLoaded({
|
||||||
|
super.categories,
|
||||||
|
super.isLoading,
|
||||||
|
super.errorMessage,
|
||||||
|
super.currentPage,
|
||||||
|
super.totalPages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoriesChildrenError extends CategoriesChildrenState {
|
||||||
|
const CategoriesChildrenError(String errorMessage)
|
||||||
|
: super(errorMessage: errorMessage);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||||
|
import 'package:proxibuy/data/repositories/category_repository.dart';
|
||||||
|
|
||||||
|
part 'categories_state.dart';
|
||||||
|
|
||||||
|
class CategoriesCubit extends Cubit<CategoriesState> {
|
||||||
|
CategoriesCubit() : super(CategoriesInitial());
|
||||||
|
|
||||||
|
void updateCategories(List<CategoriesModel> newCategories) {
|
||||||
|
emit(state.copyWith(categories: newCategories));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllCategories() async {
|
||||||
|
if (state.isLoading || state.currentPage > state.totalPages) return;
|
||||||
|
if (state.currentPage == 1) {
|
||||||
|
emit(CategoriesLoading());
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final response =
|
||||||
|
await CategoryRepository.fetchAll(page: state.currentPage);
|
||||||
|
emit(CategoriesLoaded(
|
||||||
|
categories: List.from(state.categories)..addAll(response.data ?? []),
|
||||||
|
isLoading: false,
|
||||||
|
currentPage: state.currentPage + 1,
|
||||||
|
totalPages: response.meta?.totalPages ?? 1,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(CategoriesError(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future resetPagination() async {
|
||||||
|
emit(CategoriesInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,33 +4,54 @@ class CategoriesState {
|
||||||
final List<CategoriesModel> categories;
|
final List<CategoriesModel> categories;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
final int currentPage;
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
const CategoriesState({
|
const CategoriesState({
|
||||||
this.categories = const [],
|
this.categories = const [],
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
|
this.currentPage = 1,
|
||||||
|
this.totalPages = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
CategoriesState copyWith({
|
CategoriesState copyWith({
|
||||||
List<CategoriesModel>? categories,
|
List<CategoriesModel>? categories,
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
|
int? currentPage,
|
||||||
|
int? totalPages,
|
||||||
}) {
|
}) {
|
||||||
return CategoriesState(
|
return CategoriesState(
|
||||||
categories: categories ?? this.categories,
|
categories: categories ?? this.categories,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
currentPage: currentPage ?? this.currentPage,
|
||||||
|
totalPages: totalPages ?? this.totalPages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CategoriesInitial extends CategoriesState {}
|
final class CategoriesInitial extends CategoriesState {
|
||||||
|
const CategoriesInitial()
|
||||||
|
: super(
|
||||||
|
categories: const [],
|
||||||
|
currentPage: 1,
|
||||||
|
errorMessage: null,
|
||||||
|
isLoading: false,
|
||||||
|
totalPages: 1);
|
||||||
|
}
|
||||||
|
|
||||||
final class CategoriesLoading extends CategoriesState {}
|
final class CategoriesLoading extends CategoriesState {}
|
||||||
|
|
||||||
final class CategoriesLoaded extends CategoriesState {
|
final class CategoriesLoaded extends CategoriesState {
|
||||||
const CategoriesLoaded(List<CategoriesModel> categories)
|
CategoriesLoaded({
|
||||||
: super(categories: categories);
|
super.categories,
|
||||||
|
super.isLoading,
|
||||||
|
super.errorMessage,
|
||||||
|
super.currentPage,
|
||||||
|
super.totalPages,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CategoriesError extends CategoriesState {
|
final class CategoriesError extends CategoriesState {
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||||
|
import 'package:proxibuy/data/repositories/category_repository.dart';
|
||||||
|
|
||||||
|
part 'category_state.dart';
|
||||||
|
|
||||||
|
class CategoryCubit extends Cubit<CategoryState> {
|
||||||
|
CategoryCubit() : super(CategoryInitial());
|
||||||
|
|
||||||
|
void getCategory(String id) async {
|
||||||
|
emit(CategoryLoading());
|
||||||
|
try {
|
||||||
|
final response = await CategoryRepository.fetchOne(id: id);
|
||||||
|
emit(CategorySuccess(category: response));
|
||||||
|
} catch (e) {
|
||||||
|
emit(CategoryFail(message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
part of 'category_cubit.dart';
|
||||||
|
|
||||||
|
sealed class CategoryState {}
|
||||||
|
|
||||||
|
final class CategoryInitial extends CategoryState {}
|
||||||
|
|
||||||
|
final class CategorySuccess extends CategoryState {
|
||||||
|
final CategoriesModel category;
|
||||||
|
|
||||||
|
CategorySuccess({required this.category});
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoryFail extends CategoryState {
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
CategoryFail({required this.message});
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoryLoading extends CategoryState {}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
|
||||||
import 'package:proxibuy/data/repositories/category_repository.dart';
|
|
||||||
|
|
||||||
part 'categories_state.dart';
|
|
||||||
|
|
||||||
class CategoriesCubit extends Cubit<CategoriesState> {
|
|
||||||
CategoriesCubit() : super(CategoriesInitial());
|
|
||||||
|
|
||||||
void updateCategories(List<CategoriesModel> newCategories) {
|
|
||||||
emit(state.copyWith(categories: newCategories));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> getAllCategories() async {
|
|
||||||
emit(state.copyWith(isLoading: true));
|
|
||||||
try {
|
|
||||||
final categories = await CategoryRepository.fetchAll();
|
|
||||||
if (categories != null) {
|
|
||||||
emit(CategoriesLoaded(categories));
|
|
||||||
} else {
|
|
||||||
emit(const CategoriesError('Failed to load categories'));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
emit(CategoriesError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/core/routes/app_router.dart';
|
import 'package:proxibuy/core/routes/app_router.dart';
|
||||||
import 'package:proxibuy/core/utils/empty_space.dart';
|
import 'package:proxibuy/core/utils/empty_space.dart';
|
||||||
import 'package:proxibuy/data/models/onboarding_banner_model.dart';
|
import 'package:proxibuy/data/models/onboarding_banner_model.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
||||||
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
||||||
import 'package:proxibuy/presentation/ui/widgets/edit_text/phone_number_input.dart';
|
import 'package:proxibuy/presentation/ui/widgets/edit_text/phone_number_input.dart';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,10 @@ import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/core/routes/app_router.dart';
|
import 'package:proxibuy/core/routes/app_router.dart';
|
||||||
import 'package:proxibuy/core/utils/empty_space.dart';
|
import 'package:proxibuy/core/utils/empty_space.dart';
|
||||||
import 'package:proxibuy/data/models/screen_model.dart';
|
import 'package:proxibuy/data/models/screen_model.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/widgets/navigations/categories_mega_menu.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
@ -55,65 +56,70 @@ class _HomePageState extends State<HomePage> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
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(
|
Flexible(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
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
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
8.w,
|
8.w,
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -133,24 +139,20 @@ class _HomePageState extends State<HomePage> {
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
32.w,
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:proxibuy/core/gen/assets.gen.dart';
|
import 'package:proxibuy/core/gen/assets.gen.dart';
|
||||||
import 'package:proxibuy/core/routes/app_router.dart';
|
import 'package:proxibuy/core/routes/app_router.dart';
|
||||||
import 'package:proxibuy/core/utils/empty_space.dart';
|
import 'package:proxibuy/core/utils/empty_space.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/them_mode_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/categories_cubit.dart';
|
import 'package:proxibuy/presentation/providers/category/cubit/categories_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/colors.dart';
|
import 'package:proxibuy/presentation/ui/theme/colors.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
||||||
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/widgets/default_placeholder.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
@ -52,48 +54,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
12.h,
|
12.h,
|
||||||
titleDivider(context, title: 'what\'s on your mind?', top: 16),
|
// categories(context),
|
||||||
BlocBuilder<CategoriesCubit, CategoriesState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state is CategoriesLoaded) {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 40,
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: state.categories.length,
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
physics: BouncingScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Container(
|
|
||||||
width: 200,
|
|
||||||
height: 40,
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 6),
|
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.car_crash),
|
|
||||||
8.w,
|
|
||||||
Text(state.categories[index].name ?? '')
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (state is CategoriesError) {
|
|
||||||
return Text('Failed to load categories');
|
|
||||||
} else {
|
|
||||||
return CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -633,9 +595,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
BlocBuilder<CategoriesCubit, CategoriesState>(
|
BlocBuilder<CategoriesCubit, CategoriesState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is CategoriesLoaded) {
|
if (state is CategoriesLoaded) {
|
||||||
|
if (state.categories.isEmpty) {
|
||||||
|
return Text('Empty');
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 64,
|
height: Responsive(context).isMobile() ? 64 : 40,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: state.categories.length,
|
itemCount: state.categories.length,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
|
@ -643,17 +609,54 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
physics: BouncingScrollPhysics(),
|
physics: BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
ValueNotifier<bool> isHovered = ValueNotifier(false);
|
||||||
width: 64,
|
return Padding(
|
||||||
height: 64,
|
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||||
margin: EdgeInsets.symmetric(horizontal: 6),
|
child: InkWell(
|
||||||
padding: EdgeInsets.all(8),
|
onTap: () {
|
||||||
decoration: BoxDecoration(
|
context.go(
|
||||||
borderRadius: BorderRadius.circular(8),
|
'${AppRouter.categories}/${state.categories[index].id}');
|
||||||
color: Theme.of(context).colorScheme.surface,
|
},
|
||||||
),
|
onHover: (value) {
|
||||||
child: Center(
|
isHovered.value = value;
|
||||||
child: Icon(Icons.abc),
|
},
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: isHovered,
|
||||||
|
builder: (context, hovered, _) {
|
||||||
|
return Container(
|
||||||
|
width:
|
||||||
|
Responsive(context).isMobile() ? 64 : 200,
|
||||||
|
height:
|
||||||
|
Responsive(context).isMobile() ? 64 : 40,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: hovered
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SvgPicture.network(
|
||||||
|
'${state.categories[index].url}',
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
placeholderBuilder: (context) =>
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
errorBuilder:
|
||||||
|
(context, error, stackTrace) =>
|
||||||
|
Icon(Icons.error),
|
||||||
|
),
|
||||||
|
if (!Responsive(context).isMobile()) ...[
|
||||||
|
8.w,
|
||||||
|
Text(state.categories[index].name ?? '')
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -662,7 +665,30 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
} else if (state is CategoriesError) {
|
} else if (state is CategoriesError) {
|
||||||
return Text('Failed to load categories');
|
return Text('Failed to load categories');
|
||||||
} else {
|
} else {
|
||||||
return CircularProgressIndicator();
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: Responsive(context).isMobile() ? 64 : 40,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return DefaultPlaceHolder(
|
||||||
|
child: Container(
|
||||||
|
width: Responsive(context).isMobile() ? 64 : 200,
|
||||||
|
height: Responsive(context).isMobile() ? 64 : 40,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,15 @@ class CustomColors extends ThemeExtension<CustomColors> {
|
||||||
final MaterialColor primarySwatch;
|
final MaterialColor primarySwatch;
|
||||||
final MaterialColor secondrySwatch;
|
final MaterialColor secondrySwatch;
|
||||||
final Color primaryLightSurface;
|
final Color primaryLightSurface;
|
||||||
|
final Color baseColor;
|
||||||
|
final Color highlightColor;
|
||||||
|
|
||||||
CustomColors({
|
CustomColors({
|
||||||
required this.primarySwatch,
|
required this.primarySwatch,
|
||||||
required this.secondrySwatch,
|
required this.secondrySwatch,
|
||||||
required this.primaryLightSurface,
|
required this.primaryLightSurface,
|
||||||
|
required this.baseColor,
|
||||||
|
required this.highlightColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -16,23 +20,30 @@ class CustomColors extends ThemeExtension<CustomColors> {
|
||||||
MaterialColor? primarySwatch,
|
MaterialColor? primarySwatch,
|
||||||
MaterialColor? secondrySwatch,
|
MaterialColor? secondrySwatch,
|
||||||
Color? primaryLightSurface,
|
Color? primaryLightSurface,
|
||||||
|
Color? baseColor,
|
||||||
|
Color? highlightColor,
|
||||||
}) {
|
}) {
|
||||||
return CustomColors(
|
return CustomColors(
|
||||||
primarySwatch: primarySwatch ?? this.primarySwatch,
|
primarySwatch: primarySwatch ?? this.primarySwatch,
|
||||||
secondrySwatch: secondrySwatch ?? this.secondrySwatch,
|
secondrySwatch: secondrySwatch ?? this.secondrySwatch,
|
||||||
primaryLightSurface: primaryLightSurface ?? this.primaryLightSurface,
|
primaryLightSurface: primaryLightSurface ?? this.primaryLightSurface,
|
||||||
|
baseColor: baseColor ?? this.baseColor,
|
||||||
|
highlightColor: highlightColor ?? this.highlightColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ThemeExtension<CustomColors> lerp(
|
CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
|
||||||
ThemeExtension<CustomColors>? other, double t) {
|
if (other is! CustomColors) {
|
||||||
if (other is! CustomColors) return this;
|
return this;
|
||||||
|
}
|
||||||
return CustomColors(
|
return CustomColors(
|
||||||
primarySwatch: other.primarySwatch,
|
primarySwatch: other.primarySwatch,
|
||||||
secondrySwatch: other.secondrySwatch,
|
secondrySwatch: other.secondrySwatch,
|
||||||
primaryLightSurface:
|
primaryLightSurface:
|
||||||
Color.lerp(primaryLightSurface, other.primaryLightSurface, t)!,
|
Color.lerp(primaryLightSurface, other.primaryLightSurface, t)!,
|
||||||
|
baseColor: Color.lerp(baseColor, other.baseColor, t)!,
|
||||||
|
highlightColor: Color.lerp(highlightColor, other.highlightColor, t)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ final ThemeData appTheme = ThemeData(
|
||||||
primarySwatch: primarySwatch,
|
primarySwatch: primarySwatch,
|
||||||
secondrySwatch: secondarySwatch,
|
secondrySwatch: secondarySwatch,
|
||||||
primaryLightSurface: primarySwatch[200]!,
|
primaryLightSurface: primarySwatch[200]!,
|
||||||
|
baseColor: Colors.grey[300]!,
|
||||||
|
highlightColor: Colors.grey[100]!,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -91,6 +93,8 @@ final ThemeData darkTheme = ThemeData(
|
||||||
primarySwatch: darkPrimarySwatch,
|
primarySwatch: darkPrimarySwatch,
|
||||||
secondrySwatch: darkSecondarySwatch,
|
secondrySwatch: darkSecondarySwatch,
|
||||||
primaryLightSurface: darkPrimarySwatch[600]!,
|
primaryLightSurface: darkPrimarySwatch[600]!,
|
||||||
|
baseColor: Colors.grey[700]!,
|
||||||
|
highlightColor: Colors.grey[500]!,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
||||||
|
|
||||||
|
class DefaultPlaceHolder extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final bool enabled;
|
||||||
|
final double? width;
|
||||||
|
final double? height;
|
||||||
|
const DefaultPlaceHolder(
|
||||||
|
{super.key,
|
||||||
|
required this.child,
|
||||||
|
this.enabled = true,
|
||||||
|
this.width,
|
||||||
|
this.height});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = themeColor(context);
|
||||||
|
return enabled
|
||||||
|
? IgnorePointer(
|
||||||
|
ignoring: true,
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: Shimmer.fromColors(
|
||||||
|
baseColor: colors?.baseColor ?? Colors.grey[300]!,
|
||||||
|
highlightColor: colors?.highlightColor ?? Colors.grey[100]!,
|
||||||
|
child: child),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,18 +46,6 @@ class ImageGallary extends ModalRoute<void> {
|
||||||
|
|
||||||
late final ValueNotifier<String> initialImage = ValueNotifier(initialUrl);
|
late final ValueNotifier<String> initialImage = ValueNotifier(initialUrl);
|
||||||
Widget _buildOverlayContent(BuildContext context) {
|
Widget _buildOverlayContent(BuildContext context) {
|
||||||
double x = 1;
|
|
||||||
if (MediaQuery.sizeOf(context).width < 400) {
|
|
||||||
x = 0.27;
|
|
||||||
} else if (MediaQuery.sizeOf(context).width < 600) {
|
|
||||||
x = 0.2;
|
|
||||||
} else if (Responsive(context).isMobile()) {
|
|
||||||
x = 0.5;
|
|
||||||
} else if (Responsive(context).isTablet()) {
|
|
||||||
x = 1;
|
|
||||||
} else {
|
|
||||||
x = 1.5;
|
|
||||||
}
|
|
||||||
double viewportFraction =
|
double viewportFraction =
|
||||||
(1 / (urls.length * (MediaQuery.sizeOf(context).width / 1300)));
|
(1 / (urls.length * (MediaQuery.sizeOf(context).width / 1300)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HoverSubmenu extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final List<String> submenuItems;
|
||||||
|
|
||||||
|
const HoverSubmenu(
|
||||||
|
{super.key, required this.title, required this.submenuItems});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HoverSubmenu> createState() => _HoverSubmenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HoverSubmenuState extends State<HoverSubmenu> {
|
||||||
|
OverlayEntry? _submenuOverlay;
|
||||||
|
final LayerLink _submenuLink = LayerLink();
|
||||||
|
|
||||||
|
void _showSubmenu(BuildContext context) {
|
||||||
|
_submenuOverlay = _createSubmenu();
|
||||||
|
Overlay.of(context).insert(_submenuOverlay!);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideSubmenu() {
|
||||||
|
_submenuOverlay?.remove();
|
||||||
|
_submenuOverlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverlayEntry _createSubmenu() {
|
||||||
|
return OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
left: 250, // Adjust submenu position
|
||||||
|
top: 0,
|
||||||
|
child: CompositedTransformFollower(
|
||||||
|
link: _submenuLink,
|
||||||
|
offset: Offset(200, 0),
|
||||||
|
child: MouseRegion(
|
||||||
|
onExit: (_) => _hideSubmenu(),
|
||||||
|
child: Material(
|
||||||
|
elevation: 4,
|
||||||
|
color: Colors.white,
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: widget.submenuItems
|
||||||
|
.map((item) => ListTile(
|
||||||
|
title: Text(item),
|
||||||
|
onTap: () {
|
||||||
|
_hideSubmenu();
|
||||||
|
print('$item clicked');
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CompositedTransformTarget(
|
||||||
|
link: _submenuLink,
|
||||||
|
child: MouseRegion(
|
||||||
|
onEnter: (_) => _showSubmenu(context),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(widget.title),
|
||||||
|
trailing: Icon(Icons.arrow_right),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_submenuOverlay?.remove();
|
||||||
|
_submenuOverlay = null;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,257 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.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/theme/colors.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
||||||
|
|
||||||
|
class CategoriesMegaMenu extends StatefulWidget {
|
||||||
|
const CategoriesMegaMenu({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CategoriesMegaMenu> createState() => _CategoriesMegaMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CategoriesMegaMenuState extends State<CategoriesMegaMenu> {
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
final LayerLink _layerLink = LayerLink();
|
||||||
|
ValueNotifier<bool> onHovered = ValueNotifier(false);
|
||||||
|
late ValueNotifier<String?> selectedId = ValueNotifier(null);
|
||||||
|
bool visibleMenu = false;
|
||||||
|
void _showMegaMenu() {
|
||||||
|
_overlayEntry = _createMegaMenu();
|
||||||
|
Overlay.of(context).insert(_overlayEntry!);
|
||||||
|
visibleMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideMegaMenu() {
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
_overlayEntry = null;
|
||||||
|
visibleMenu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
selectedId.value =
|
||||||
|
context.read<CategoriesCubit>().state.categories.first.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
context.read<CategoriesCubit>().stream.listen(
|
||||||
|
(state) {
|
||||||
|
if (!state.isLoading && state.categories.isNotEmpty) {
|
||||||
|
selectedId.value = state.categories.first.id!;
|
||||||
|
if (mounted) {
|
||||||
|
context
|
||||||
|
.read<CategoriesChildrenCubit>()
|
||||||
|
.getAllChildCategories(selectedId.value!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hideMegaMenu();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
OverlayEntry _createMegaMenu() {
|
||||||
|
return OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
width: 800, // Adjust width of the mega menu
|
||||||
|
child: CompositedTransformFollower(
|
||||||
|
link: _layerLink,
|
||||||
|
offset: Offset(0, 50), // Adjust position below the button
|
||||||
|
child: MouseRegion(
|
||||||
|
onExit: (_) => _hideMegaMenu(),
|
||||||
|
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');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _menuItem(String title) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(title),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CompositedTransformTarget(
|
||||||
|
link: _layerLink,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {},
|
||||||
|
onHover: (value) {
|
||||||
|
onHovered.value = value;
|
||||||
|
},
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: onHovered,
|
||||||
|
builder: (context, hovered, _) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: hovered
|
||||||
|
? themeColor(context)?.primaryLightSurface.withAlpha(90)
|
||||||
|
: null),
|
||||||
|
child: IconButton(
|
||||||
|
splashRadius: 12,
|
||||||
|
onPressed: () {
|
||||||
|
if (visibleMenu) {
|
||||||
|
_hideMegaMenu();
|
||||||
|
} else {
|
||||||
|
_showMegaMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.category,
|
||||||
|
color: hovered
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Theme.of(context).colorScheme.onSurface),
|
||||||
|
12.w,
|
||||||
|
Text(
|
||||||
|
'Categories',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelLarge
|
||||||
|
?.copyWith(
|
||||||
|
color: hovered
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:ll_dropdown_menu/ll_dropdown_menu.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// Wrapper AppBar for customizing default values
|
||||||
|
class WrapperAppBar extends AppBar {
|
||||||
|
static Widget? _defaultLeading;
|
||||||
|
static Widget? _defaultTitle;
|
||||||
|
static TextStyle? _defaultToolbarTextStyle;
|
||||||
|
static TextStyle? _defaultTitleTextStyle;
|
||||||
|
static double? _defaultToolbarHeight;
|
||||||
|
static SystemUiOverlayStyle? _defaultSystemOverlayStyle;
|
||||||
|
|
||||||
|
static void initConfig({
|
||||||
|
Widget? defaultLeading,
|
||||||
|
Widget? defaultTitle,
|
||||||
|
TextStyle? defaultToolbarTextStyle,
|
||||||
|
TextStyle? defaultTitleTextStyle,
|
||||||
|
double? defaultToolbarHeight,
|
||||||
|
SystemUiOverlayStyle? defaultSystemOverlayStyle,
|
||||||
|
}) {
|
||||||
|
_defaultLeading = defaultLeading;
|
||||||
|
_defaultTitle = defaultTitle;
|
||||||
|
_defaultToolbarTextStyle = defaultToolbarTextStyle;
|
||||||
|
_defaultTitleTextStyle = defaultTitleTextStyle;
|
||||||
|
_defaultToolbarHeight = defaultToolbarHeight;
|
||||||
|
_defaultSystemOverlayStyle = defaultSystemOverlayStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
WrapperAppBar({
|
||||||
|
super.key,
|
||||||
|
Widget? leading,
|
||||||
|
super.automaticallyImplyLeading,
|
||||||
|
Widget? title,
|
||||||
|
super.actions,
|
||||||
|
super.flexibleSpace,
|
||||||
|
super.bottom,
|
||||||
|
double super.elevation = 0,
|
||||||
|
super.scrolledUnderElevation,
|
||||||
|
super.notificationPredicate,
|
||||||
|
super.shadowColor,
|
||||||
|
super.surfaceTintColor,
|
||||||
|
super.shape,
|
||||||
|
Color super.backgroundColor = Colors.white,
|
||||||
|
super.foregroundColor,
|
||||||
|
super.iconTheme,
|
||||||
|
super.actionsIconTheme,
|
||||||
|
super.primary,
|
||||||
|
bool super.centerTitle = true,
|
||||||
|
super.excludeHeaderSemantics,
|
||||||
|
super.titleSpacing,
|
||||||
|
super.toolbarOpacity,
|
||||||
|
super.bottomOpacity,
|
||||||
|
double? toolbarHeight,
|
||||||
|
super.leadingWidth,
|
||||||
|
TextStyle? toolbarTextStyle,
|
||||||
|
TextStyle? titleTextStyle,
|
||||||
|
SystemUiOverlayStyle? systemOverlayStyle,
|
||||||
|
super.forceMaterialTransparency,
|
||||||
|
super.clipBehavior,
|
||||||
|
String? titleText,
|
||||||
|
}) : super(
|
||||||
|
leading:
|
||||||
|
leading ?? (automaticallyImplyLeading ? _defaultLeading : null),
|
||||||
|
title: title ??
|
||||||
|
_defaultTitle ??
|
||||||
|
Text(titleText ?? '', style: titleTextStyle),
|
||||||
|
toolbarHeight: toolbarHeight ?? _defaultToolbarHeight,
|
||||||
|
toolbarTextStyle: toolbarTextStyle ?? _defaultToolbarTextStyle,
|
||||||
|
titleTextStyle: titleTextStyle ??
|
||||||
|
_defaultTitleTextStyle ??
|
||||||
|
const TextStyle(color: Colors.black, fontSize: 18),
|
||||||
|
systemOverlayStyle: systemOverlayStyle ?? _defaultSystemOverlayStyle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DropDownDemo3 extends StatefulWidget {
|
||||||
|
const DropDownDemo3({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DropDownDemo3> createState() => _DropDownDemoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DropDownDemoState extends State<DropDownDemo3>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final DropDownController dropDownController = DropDownController();
|
||||||
|
final DropDownCascadeListDataController dataController1 =
|
||||||
|
DropDownCascadeListDataController();
|
||||||
|
final DropDownCascadeListDataController dataController2 =
|
||||||
|
DropDownCascadeListDataController();
|
||||||
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||||
|
|
||||||
|
List<DropDownItem<List<DropDownItem>>> data1 = List.generate(
|
||||||
|
6,
|
||||||
|
(index) => DropDownItem<List<DropDownItem>>(
|
||||||
|
text: "Tab $index",
|
||||||
|
data: List.generate(
|
||||||
|
index + 2,
|
||||||
|
(index) => DropDownItem(
|
||||||
|
text: "Tab Second $index",
|
||||||
|
activeIcon: const Icon(Icons.check),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
List<DropDownItem<List<DropDownItem>>> data2 = List.generate(
|
||||||
|
6,
|
||||||
|
(index) => DropDownItem<List<DropDownItem>>(
|
||||||
|
text: "Tab $index",
|
||||||
|
data: List.generate(
|
||||||
|
index + 2,
|
||||||
|
(index) => DropDownItem(
|
||||||
|
text: "Tab Second $index",
|
||||||
|
activeIcon: const Icon(Icons.check),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
key: scaffoldKey,
|
||||||
|
appBar: WrapperAppBar(
|
||||||
|
titleText: "DropDownDemo Custom",
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
actions: const <Widget>[
|
||||||
|
SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(children: [
|
||||||
|
DropDownHeader(
|
||||||
|
controller: dropDownController,
|
||||||
|
boxStyle: const DropDownBoxStyle(
|
||||||
|
height: 50,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
),
|
||||||
|
itemStyle: DropDownItemStyle(
|
||||||
|
activeIconColor: Colors.blue,
|
||||||
|
activeTextStyle: const TextStyle(color: Colors.blue),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
activeDecoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
highlightTextStyle: const TextStyle(color: Colors.white),
|
||||||
|
highlightDecoration: BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
highlightIcon: const Icon(
|
||||||
|
Icons.arrow_drop_down,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items: List.generate(
|
||||||
|
3,
|
||||||
|
(index) => DropDownItem<Tab>(
|
||||||
|
text: index == 2 ? "Filter" : "Tab $index",
|
||||||
|
icon: index == 2
|
||||||
|
? const Icon(Icons.filter_alt)
|
||||||
|
: const Icon(Icons.arrow_drop_down),
|
||||||
|
activeIcon: index == 2
|
||||||
|
? const Icon(Icons.filter_alt)
|
||||||
|
: const Icon(Icons.arrow_drop_up),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onItemTap: (index, item) {
|
||||||
|
if (index == 2) {
|
||||||
|
dropDownController.hide();
|
||||||
|
scaffoldKey.currentState?.openEndDrawer();
|
||||||
|
} else {
|
||||||
|
dropDownController.toggle(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Colors.blue[200],
|
||||||
|
child: Center(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
dataController1.resetAllItemsStatus();
|
||||||
|
dataController2.resetAllItemsStatus();
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
dropDownController.hide(
|
||||||
|
index: i,
|
||||||
|
status: DropDownHeaderStatus(text: "Tab$i"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text("Reset"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropDownView(
|
||||||
|
controller: dropDownController,
|
||||||
|
builders: [
|
||||||
|
DropDownCascadeList(
|
||||||
|
controller: dropDownController,
|
||||||
|
dataController: dataController1,
|
||||||
|
headerIndex: 0,
|
||||||
|
secondFloorItemStyle: const DropDownItemStyle(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
activeBackgroundColor: Color(0xFFF5F5F5),
|
||||||
|
activeTextStyle:
|
||||||
|
TextStyle(fontSize: 14, color: Colors.blue),
|
||||||
|
activeIconColor: Colors.blue,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
textExpand: true,
|
||||||
|
),
|
||||||
|
items: data1,
|
||||||
|
),
|
||||||
|
DropDownCascadeList(
|
||||||
|
controller: dropDownController,
|
||||||
|
dataController: dataController2,
|
||||||
|
headerIndex: 1,
|
||||||
|
secondFloorItemStyle: const DropDownItemStyle(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
activeBackgroundColor: Color(0xFFF5F5F5),
|
||||||
|
activeTextStyle:
|
||||||
|
TextStyle(fontSize: 14, color: Colors.blue),
|
||||||
|
activeIconColor: Colors.blue,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
textExpand: true,
|
||||||
|
),
|
||||||
|
items: data2,
|
||||||
|
maxMultiChoiceSize: 3,
|
||||||
|
),
|
||||||
|
DropDownViewWrapper(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: 300,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.yellow,
|
||||||
|
height: 300,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
dropDownController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MyMegaMenu extends StatelessWidget {
|
||||||
|
const MyMegaMenu({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 300,
|
||||||
|
height: 120,
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
value: 'Menu',
|
||||||
|
icon: Icon(Icons.av_timer),
|
||||||
|
iconSize: 24,
|
||||||
|
elevation: 16,
|
||||||
|
style: TextStyle(color: Colors.deepPurple),
|
||||||
|
underline: Container(
|
||||||
|
height: 2,
|
||||||
|
color: Colors.deepPurpleAccent,
|
||||||
|
),
|
||||||
|
onChanged: (newValue) {},
|
||||||
|
items: <String>['Menu', 'Home', 'Profile', 'Settings']
|
||||||
|
.map<DropdownMenuItem<String>>((String value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: Text(value),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
..add(DropdownMenuItem(
|
||||||
|
child: ExpansionTile(
|
||||||
|
title: Text('Menu'),
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('Submenu 1'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Submenu 2'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -544,6 +544,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
ll_dropdown_menu:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: ll_dropdown_menu
|
||||||
|
sha256: ae752c626b8207a86479013efce9b7a3181ac9eb864756f1071934bec2d954db
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -832,6 +840,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
shimmer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shimmer
|
||||||
|
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ dependencies:
|
||||||
pretty_dio_logger: ^1.4.0
|
pretty_dio_logger: ^1.4.0
|
||||||
universal_html: ^2.2.4
|
universal_html: ^2.2.4
|
||||||
dropdown_textfield: ^1.2.0
|
dropdown_textfield: ^1.2.0
|
||||||
|
ll_dropdown_menu: ^0.8.0
|
||||||
|
shimmer: ^3.0.0
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: true
|
android: true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue