create gallary slider and call categories api.
This commit is contained in:
parent
25b5fe1160
commit
5b993e5c4c
|
|
@ -1,6 +1,5 @@
|
||||||
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_native_splash/flutter_native_splash.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/cubit/user_info_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/screens/auth/auth_page.dart';
|
import 'package:proxibuy/presentation/ui/screens/auth/auth_page.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
class ApiRoutes {
|
class ApiRoutes {
|
||||||
static final userInfo = '/userinfo';
|
static final userInfo = '/userinfo';
|
||||||
|
static final category = '/category';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,19 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
final keycloak = 'https://keycloak.liara.run/';
|
final baseUrl = 'https://lba-api.liara.run';
|
||||||
final clientID = 'frontend';
|
|
||||||
final realm = 'lba';
|
|
||||||
|
|
||||||
late Dio _dio;
|
late Dio _dio;
|
||||||
|
|
||||||
void setAuthToken(String token) {
|
void setAuthToken(String token) {
|
||||||
_dio.options.headers['Authorization'] = 'Bearer $token';
|
_dio.options.headers['Authorization'] =
|
||||||
|
'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJheC1zX3N3Tm5fU1hDTVkzWFowSDVKekNhQ0psVXh6bmZ0WHBxSk1YUEF3In0.eyJleHAiOjE3Mzk4ODcwNzksImlhdCI6MTczOTg4NTI3OSwianRpIjoiYTlmZGE4NzItZmJhZC00ZmQ5LTg3MTMtMTcwYjM3MWE1NTM2IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5saWFyYS5ydW4vcmVhbG1zL2xiYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmMzljODIxNi0zODhhLTQ0ZTEtODVhOC00Zjk5NmU2NmU2MDQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmcm9udGVuZCIsInNpZCI6IjIwNWNmNTBkLTE5MWUtNGViMS1iODBkLTMzMTFiNjIzYTZhMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sYmEtYXBpLmxpYXJhLnJ1bi8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiLCIvKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYmEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19LCJmcm9udGVuZCI6eyJyb2xlcyI6WyJzaG9wIiwidXNlciJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImRlbW8gZGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8iLCJnaXZlbl9uYW1lIjoiZGVtbyIsImZhbWlseV9uYW1lIjoiZGVtbyIsImVtYWlsIjoiZGVtb0BnbWFpbC5jb20ifQ.r_RtuJLZ9HQZdoy1Fi29d7hbXqcQ497XThKUmAjd_ClenE2VjpTzpogTOFwrrzJqRxm9fDpBflliqBWRg7KwR7irxW4HHBBaJmCaLzLbG8EtiuvciwDc9ugqLwUvGs2Gnzc7P0RA9aWzVY2lBkDZa7GeEvCCI4k0pbSWsAjX-Ax2vHxCqW8GzMsBeLkQ2BE9cyKX5Q3f-yne5HjDoxF1350qPlXwxIRkmM2Ct-aiMm7CjCaawPdTqdritsWejwTtaVCQtzkHPyeToPwE_X1YLWDlFdwpWSzjiI2fDFyV-MykLgDZevxHtvFoIg-2f6Zsm2_t7AMoCr-tM7vquCZTaw';
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiService() {
|
ApiService() {
|
||||||
_dio = Dio(
|
_dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl:
|
baseUrl: baseUrl, // 🔹 Set your API base URL
|
||||||
'$keycloak/$realm/protocol/openid-connect', // 🔹 Set your API base URL
|
|
||||||
connectTimeout: const Duration(seconds: 10), // Timeout settings
|
connectTimeout: const Duration(seconds: 10), // Timeout settings
|
||||||
receiveTimeout: const Duration(seconds: 10),
|
receiveTimeout: const Duration(seconds: 10),
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -30,6 +28,7 @@ class ApiService {
|
||||||
)..interceptors.add(PrettyDioLogger(
|
)..interceptors.add(PrettyDioLogger(
|
||||||
enabled: kDebugMode,
|
enabled: kDebugMode,
|
||||||
));
|
));
|
||||||
|
setAuthToken('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🔹 Handle GET requests
|
/// 🔹 Handle GET requests
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
class ResponseModel<T> {
|
||||||
|
T? data;
|
||||||
|
Meta? meta;
|
||||||
|
Links? links;
|
||||||
|
|
||||||
|
ResponseModel({this.data, this.meta, this.links});
|
||||||
|
|
||||||
|
ResponseModel.fromJson(
|
||||||
|
Map<String, dynamic> json, T Function(dynamic) fromJsonT) {
|
||||||
|
data = json['data'] != null ? fromJsonT(json['data']) : null;
|
||||||
|
meta = json['meta'] != null ? Meta.fromJson(json['meta']) : null;
|
||||||
|
links = json['links'] != null ? Links.fromJson(json['links']) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson(Map<String, dynamic> Function(T) toJsonT) {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
if (this.data != null) {
|
||||||
|
data['data'] = toJsonT(this.data as T);
|
||||||
|
}
|
||||||
|
if (meta != null) {
|
||||||
|
data['meta'] = meta!.toJson();
|
||||||
|
}
|
||||||
|
if (links != null) {
|
||||||
|
data['links'] = links!.toJson();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta {
|
||||||
|
int? itemsPerPage;
|
||||||
|
int? totalItems;
|
||||||
|
int? currentPage;
|
||||||
|
int? totalPages;
|
||||||
|
List<List<String>>? sortBy;
|
||||||
|
|
||||||
|
Meta(
|
||||||
|
{this.itemsPerPage,
|
||||||
|
this.totalItems,
|
||||||
|
this.currentPage,
|
||||||
|
this.totalPages,
|
||||||
|
this.sortBy});
|
||||||
|
|
||||||
|
Meta.fromJson(Map<String, dynamic> json) {
|
||||||
|
itemsPerPage = json['itemsPerPage'];
|
||||||
|
totalItems = json['totalItems'];
|
||||||
|
currentPage = json['currentPage'];
|
||||||
|
totalPages = json['totalPages'];
|
||||||
|
if (json['sortBy'] != null) {
|
||||||
|
// sortBy = <List<String>>[];
|
||||||
|
// json['sortBy'].forEach((v) {
|
||||||
|
// sortBy!.addAll(v.toList());
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['itemsPerPage'] = itemsPerPage;
|
||||||
|
data['totalItems'] = totalItems;
|
||||||
|
data['currentPage'] = currentPage;
|
||||||
|
data['totalPages'] = totalPages;
|
||||||
|
if (sortBy != null) {
|
||||||
|
data['sortBy'] = sortBy!.map((v) => v).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Links {
|
||||||
|
String? current;
|
||||||
|
|
||||||
|
Links({this.current});
|
||||||
|
|
||||||
|
Links.fromJson(Map<String, dynamic> json) {
|
||||||
|
current = json['current'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['current'] = current;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
class CategoriesModel {
|
||||||
|
String? id;
|
||||||
|
String? name;
|
||||||
|
int? count;
|
||||||
|
String? parentId;
|
||||||
|
|
||||||
|
CategoriesModel({this.id, this.name, this.count, this.parentId});
|
||||||
|
|
||||||
|
CategoriesModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
name = json['name'];
|
||||||
|
count = json['count'];
|
||||||
|
parentId = json['parent_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['id'] = id;
|
||||||
|
data['name'] = name;
|
||||||
|
data['count'] = count;
|
||||||
|
data['parent_id'] = parentId;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:proxibuy/core/services/api/api_routes.dart';
|
||||||
|
import 'package:proxibuy/core/services/api/api_service.dart';
|
||||||
|
import 'package:proxibuy/core/services/api/response_model.dart';
|
||||||
|
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||||
|
|
||||||
|
class CategoryRepository {
|
||||||
|
static Future<List<CategoriesModel>?> fetchAll() async {
|
||||||
|
try {
|
||||||
|
var response = await apiService.get(ApiRoutes.category);
|
||||||
|
print("Users: $response");
|
||||||
|
final res = ResponseModel<List<CategoriesModel>>.fromJson(
|
||||||
|
response,
|
||||||
|
(data) {
|
||||||
|
return (data as List)
|
||||||
|
.map((item) => CategoriesModel.fromJson(item))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
print("Error: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ 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/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/providers/cubit/user_info_cubit.dart';
|
import 'package:proxibuy/presentation/providers/cubit/user_info_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
import 'package:proxibuy/presentation/ui/theme/theme.dart';
|
||||||
|
|
@ -18,6 +19,7 @@ void main() async {
|
||||||
runApp(MultiBlocProvider(
|
runApp(MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ThemModeCubit>(create: (context) => ThemModeCubit()),
|
BlocProvider<ThemModeCubit>(create: (context) => ThemModeCubit()),
|
||||||
|
BlocProvider<CategoriesCubit>(create: (context) => CategoriesCubit()),
|
||||||
BlocProvider<UserInfoCubit>(
|
BlocProvider<UserInfoCubit>(
|
||||||
create: (context) => UserInfoCubit()..getUserInfo()),
|
create: (context) => UserInfoCubit()..getUserInfo()),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
part of 'categories_cubit.dart';
|
||||||
|
|
||||||
|
class CategoriesState {
|
||||||
|
final List<CategoriesModel> categories;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const CategoriesState({
|
||||||
|
this.categories = const [],
|
||||||
|
this.isLoading = false,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
CategoriesState copyWith({
|
||||||
|
List<CategoriesModel>? categories,
|
||||||
|
bool? isLoading,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return CategoriesState(
|
||||||
|
categories: categories ?? this.categories,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoriesInitial extends CategoriesState {}
|
||||||
|
|
||||||
|
final class CategoriesLoading extends CategoriesState {}
|
||||||
|
|
||||||
|
final class CategoriesLoaded extends CategoriesState {
|
||||||
|
const CategoriesLoaded(List<CategoriesModel> categories)
|
||||||
|
: super(categories: categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CategoriesError extends CategoriesState {
|
||||||
|
const CategoriesError(String errorMessage)
|
||||||
|
: super(errorMessage: errorMessage);
|
||||||
|
}
|
||||||
|
|
@ -214,7 +214,7 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
onBoarding = false;
|
onBoarding = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageBuilder: (context, item) {
|
onPageBuilder: (context, item, _) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ 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/cubit/them_mode_cubit.dart';
|
||||||
import 'package:proxibuy/presentation/ui/screens/home/explore_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/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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,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/presentation/providers/cubit/them_mode_cubit.dart';
|
import 'package:proxibuy/presentation/providers/cubit/them_mode_cubit.dart';
|
||||||
|
import 'package:proxibuy/presentation/providers/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';
|
||||||
|
|
@ -18,9 +19,20 @@ class HomeScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.read<CategoriesCubit>().getAllCategories();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -32,6 +44,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
Scaffold desktop(BuildContext context) {
|
Scaffold desktop(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
physics: BouncingScrollPhysics(),
|
physics: BouncingScrollPhysics(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
|
|
@ -40,36 +53,46 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
children: [
|
children: [
|
||||||
12.h,
|
12.h,
|
||||||
titleDivider(context, title: 'what\'s on your mind?', top: 16),
|
titleDivider(context, title: 'what\'s on your mind?', top: 16),
|
||||||
SizedBox(
|
BlocBuilder<CategoriesCubit, CategoriesState>(
|
||||||
width: double.infinity,
|
builder: (context, state) {
|
||||||
height: 40,
|
if (state is CategoriesLoaded) {
|
||||||
child: ListView.builder(
|
return SizedBox(
|
||||||
itemCount: 20,
|
width: double.infinity,
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
physics: BouncingScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Container(
|
|
||||||
width: 200,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
margin: EdgeInsets.symmetric(horizontal: 6),
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.all(8),
|
itemCount: state.categories.length,
|
||||||
decoration: BoxDecoration(
|
scrollDirection: Axis.horizontal,
|
||||||
borderRadius: BorderRadius.circular(8),
|
shrinkWrap: true,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
),
|
physics: BouncingScrollPhysics(),
|
||||||
child: Row(
|
itemBuilder: (context, index) {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return Container(
|
||||||
children: [
|
width: 200,
|
||||||
Icon(Icons.car_crash),
|
height: 40,
|
||||||
8.w,
|
margin: EdgeInsets.symmetric(horizontal: 6),
|
||||||
Text('Category')
|
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,
|
||||||
|
|
@ -84,7 +107,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
items: ['1', '2', '3', '4'],
|
items: ['1', '2', '3', '4'],
|
||||||
height: 320,
|
height: 320,
|
||||||
viewportFraction: 0.9,
|
viewportFraction: 0.9,
|
||||||
onPageBuilder: (context, item) {
|
onPageBuilder: (context, item, _) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
|
|
@ -118,7 +141,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
items: ['1', '2', '3', '4'],
|
items: ['1', '2', '3', '4'],
|
||||||
height: 280,
|
height: 280,
|
||||||
viewportFraction: 0.9,
|
viewportFraction: 0.9,
|
||||||
onPageBuilder: (context, item) {
|
onPageBuilder: (context, item, _) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
|
|
@ -145,7 +168,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
items: ['1', '2', '3', '4'],
|
items: ['1', '2', '3', '4'],
|
||||||
height: 280,
|
height: 280,
|
||||||
viewportFraction: 0.9,
|
viewportFraction: 0.9,
|
||||||
onPageBuilder: (context, item) {
|
onPageBuilder: (context, item, _) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
|
|
@ -197,66 +220,73 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
10.w
|
10.w
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: RefreshIndicator(
|
||||||
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
onRefresh: () async {
|
||||||
child: Column(
|
context.read<CategoriesCubit>().getAllCategories();
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
},
|
||||||
children: [
|
child: SingleChildScrollView(
|
||||||
searchBar(context),
|
controller: _scrollController,
|
||||||
categories(context),
|
physics:
|
||||||
titleDivider(context, title: 'Top 10 Discount & Offers'),
|
BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||||
CarouselSliderWidget<String>(
|
child: Column(
|
||||||
items: ['1', '2', '3', '4'],
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
height: 180,
|
children: [
|
||||||
viewportFraction: 0.8,
|
searchBar(context),
|
||||||
onPageBuilder: (context, item) {
|
categories(context),
|
||||||
return ClipRRect(
|
titleDivider(context, title: 'Top 10 Discount & Offers'),
|
||||||
borderRadius: BorderRadius.circular(16),
|
CarouselSliderWidget<String>(
|
||||||
child: Image.network(
|
items: ['1', '2', '3', '4'],
|
||||||
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
height: 180,
|
||||||
fit: BoxFit.cover,
|
viewportFraction: 0.8,
|
||||||
width: double.infinity,
|
onPageBuilder: (context, item, _) {
|
||||||
),
|
return ClipRRect(
|
||||||
);
|
borderRadius: BorderRadius.circular(16),
|
||||||
},
|
child: Image.network(
|
||||||
),
|
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
||||||
flashSales(context, top: 12),
|
fit: BoxFit.cover,
|
||||||
titleDivider(context, title: 'special discount'),
|
width: double.infinity,
|
||||||
CarouselSliderWidget<String>(
|
),
|
||||||
items: ['1', '2', '3', '4'],
|
);
|
||||||
height: 180,
|
},
|
||||||
viewportFraction: 0.8,
|
),
|
||||||
onPageBuilder: (context, item) {
|
flashSales(context, top: 12),
|
||||||
return ClipRRect(
|
titleDivider(context, title: 'special discount'),
|
||||||
borderRadius: BorderRadius.circular(16),
|
CarouselSliderWidget<String>(
|
||||||
child: Image.network(
|
items: ['1', '2', '3', '4'],
|
||||||
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
height: 180,
|
||||||
fit: BoxFit.cover,
|
viewportFraction: 0.8,
|
||||||
width: double.infinity,
|
onPageBuilder: (context, item, _) {
|
||||||
),
|
return ClipRRect(
|
||||||
);
|
borderRadius: BorderRadius.circular(16),
|
||||||
},
|
child: Image.network(
|
||||||
),
|
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
||||||
seasonalDiscounts(context, top: 12),
|
fit: BoxFit.cover,
|
||||||
titleDivider(context, title: 'Crafting something for you'),
|
width: double.infinity,
|
||||||
CarouselSliderWidget<String>(
|
),
|
||||||
items: ['1', '2', '3', '4'],
|
);
|
||||||
height: 180,
|
},
|
||||||
viewportFraction: 0.8,
|
),
|
||||||
onPageBuilder: (context, item) {
|
seasonalDiscounts(context, top: 12),
|
||||||
return ClipRRect(
|
titleDivider(context, title: 'Crafting something for you'),
|
||||||
borderRadius: BorderRadius.circular(16),
|
CarouselSliderWidget<String>(
|
||||||
child: Image.network(
|
items: ['1', '2', '3', '4'],
|
||||||
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
height: 180,
|
||||||
fit: BoxFit.cover,
|
viewportFraction: 0.8,
|
||||||
width: double.infinity,
|
onPageBuilder: (context, item, _) {
|
||||||
),
|
return ClipRRect(
|
||||||
);
|
borderRadius: BorderRadius.circular(16),
|
||||||
},
|
child: Image.network(
|
||||||
),
|
'https://pixcap.com/cdn/library/templates/f1ee999f-4f9e-4b5d-83b5-51d04681a999/thumbnail/9158b87d-1079-4973-9514-ee4ada0adf54_transparent_1422_800.webp',
|
||||||
firstPurchaseDiscount(context),
|
fit: BoxFit.cover,
|
||||||
12.h
|
width: double.infinity,
|
||||||
],
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
firstPurchaseDiscount(context),
|
||||||
|
12.h
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -600,31 +630,41 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
titleDivider(context, title: 'what\'s on your mind?', top: 16),
|
titleDivider(context, title: 'what\'s on your mind?', top: 16),
|
||||||
SizedBox(
|
BlocBuilder<CategoriesCubit, CategoriesState>(
|
||||||
width: double.infinity,
|
builder: (context, state) {
|
||||||
height: 64,
|
if (state is CategoriesLoaded) {
|
||||||
child: ListView.builder(
|
return SizedBox(
|
||||||
itemCount: 20,
|
width: double.infinity,
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
physics: BouncingScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Container(
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
height: 64,
|
||||||
margin: EdgeInsets.symmetric(horizontal: 6),
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.all(8),
|
itemCount: state.categories.length,
|
||||||
decoration: BoxDecoration(
|
scrollDirection: Axis.horizontal,
|
||||||
borderRadius: BorderRadius.circular(8),
|
shrinkWrap: true,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
),
|
physics: BouncingScrollPhysics(),
|
||||||
child: Center(
|
itemBuilder: (context, index) {
|
||||||
child: Icon(Icons.abc),
|
return Container(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(Icons.abc),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
} else if (state is CategoriesError) {
|
||||||
),
|
return Text('Failed to load categories');
|
||||||
|
} else {
|
||||||
|
return CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:proxibuy/core/utils/empty_space.dart';
|
import 'package:proxibuy/core/utils/empty_space.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/dialog/image_gallary.dart';
|
||||||
|
|
||||||
class ProductPage extends StatelessWidget {
|
class ProductPage extends StatelessWidget {
|
||||||
const ProductPage({super.key});
|
const ProductPage({super.key});
|
||||||
|
|
@ -57,15 +57,30 @@ class ProductPage extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(16),
|
onTap: () {
|
||||||
child: AspectRatio(
|
Navigator.of(context).push(ImageGallary(
|
||||||
aspectRatio: 1 / 1,
|
initialUrl:
|
||||||
child: Image.network(
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
urls: [
|
||||||
width: double.infinity,
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
fit: BoxFit.cover,
|
...List.generate(
|
||||||
)),
|
10,
|
||||||
|
(index) =>
|
||||||
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1 / 1,
|
||||||
|
child: Image.network(
|
||||||
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -81,19 +96,34 @@ class ProductPage extends StatelessWidget {
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: BouncingScrollPhysics(),
|
physics: BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return InkWell(
|
||||||
margin:
|
onTap: () {
|
||||||
EdgeInsets.symmetric(horizontal: 8),
|
Navigator.of(context).push(ImageGallary(
|
||||||
padding: EdgeInsets.all(4),
|
initialUrl:
|
||||||
decoration: BoxDecoration(
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
color: Theme.of(context)
|
urls: [
|
||||||
.colorScheme
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
.surface
|
...List.generate(
|
||||||
.withAlpha(200),
|
10,
|
||||||
borderRadius:
|
(index) =>
|
||||||
BorderRadius.circular(8)),
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
child: Image.network(
|
)
|
||||||
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png'),
|
]));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin:
|
||||||
|
EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withAlpha(200),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8)),
|
||||||
|
child: Image.network(
|
||||||
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -117,13 +147,28 @@ class ProductPage extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 46),
|
padding: const EdgeInsets.only(bottom: 46),
|
||||||
child: AspectRatio(
|
child: InkWell(
|
||||||
aspectRatio: 0.95,
|
onTap: () {
|
||||||
child: Image.network(
|
Navigator.of(context).push(ImageGallary(
|
||||||
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
initialUrl:
|
||||||
width: double.infinity,
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
fit: BoxFit.cover,
|
urls: [
|
||||||
)),
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
|
...List.generate(
|
||||||
|
10,
|
||||||
|
(index) =>
|
||||||
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 0.95,
|
||||||
|
child: Image.network(
|
||||||
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 16,
|
left: 16,
|
||||||
|
|
@ -176,17 +221,32 @@ class ProductPage extends StatelessWidget {
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: BouncingScrollPhysics(),
|
physics: BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return InkWell(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 8),
|
onTap: () {
|
||||||
padding: EdgeInsets.all(4),
|
Navigator.of(context).push(ImageGallary(
|
||||||
decoration: BoxDecoration(
|
initialUrl:
|
||||||
color: Theme.of(context)
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
.colorScheme
|
urls: [
|
||||||
.surface
|
'https://s3-alpha-sig.figma.com/img/4361/9bde/26f7810ea7f22d8aa59a907d378c16ff?Expires=1740960000&Key-Pair-Id=APKAQ4GOSFWCW27IBOMQ&Signature=DIm2QGXae9cxWDNV2UoJd4JWpvvmL~qKmEefvrtVw9XwKTVHwcfgqR3-s-5s7jqsTiT0iWDsYY10MeuSf2d4xXx9uTra~QEj3LbO1x4cRv4DBDJ-wkLGxliV07HVWl~CMl1MhzXyfUvWkrR30QQ5x3IslvBpfulMj88AYU5l97XPaNMOcMT8YqErdCdpiqdXBM9L2vUpCUbZGH~SfKCQyl1QcNhuCBTPODPWcK6kGU1ell3qwXC0-4JcLiiR-Kdo9asKy495iVtQ584fyZAdeN1R4oJnVGnAlUyN2HRQh6j518FCrJIzr7O0JajvrmwZ7j48nXjWPtG0SGp8Lq4l6A__',
|
||||||
.withAlpha(200),
|
...List.generate(
|
||||||
borderRadius: BorderRadius.circular(8)),
|
10,
|
||||||
child: Image.network(
|
(index) =>
|
||||||
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png'),
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png',
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withAlpha(200),
|
||||||
|
borderRadius: BorderRadius.circular(8)),
|
||||||
|
child: Image.network(
|
||||||
|
'https://static.vecteezy.com/system/resources/previews/021/275/832/non_2x/running-shoes-illustration-with-fire-shape-yellow-and-red-isolated-on-transparan-background-free-png.png'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -295,7 +355,7 @@ class ProductPage extends StatelessWidget {
|
||||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
child: Flex(
|
child: Flex(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ class Responsive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isMobile() => MediaQuery.sizeOf(context).width < 904;
|
bool isMobile() => MediaQuery.sizeOf(context).width < 768;
|
||||||
bool isTablet() =>
|
bool isTablet() =>
|
||||||
MediaQuery.sizeOf(context).width < 1280 &&
|
MediaQuery.sizeOf(context).width < 1024 &&
|
||||||
MediaQuery.sizeOf(context).width >= 904;
|
MediaQuery.sizeOf(context).width >= 768;
|
||||||
bool isDesktop() => MediaQuery.sizeOf(context).width >= 1280;
|
bool isDesktop() => MediaQuery.sizeOf(context).width >= 1024;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,18 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||||
|
|
||||||
class CarouselSliderWidget<T> extends StatefulWidget {
|
class CarouselSliderWidget<T> extends StatefulWidget {
|
||||||
final List<T> items;
|
final List<T> items;
|
||||||
final Widget Function(
|
final Widget Function(BuildContext context, T item, int index) onPageBuilder;
|
||||||
BuildContext context,
|
|
||||||
T item,
|
|
||||||
) onPageBuilder;
|
|
||||||
final bool withNavs;
|
final bool withNavs;
|
||||||
final double height;
|
final bool withInds;
|
||||||
|
final double? height;
|
||||||
final double viewportFraction;
|
final double viewportFraction;
|
||||||
|
final double aspectRatio;
|
||||||
|
final bool enlargeCenterPage;
|
||||||
final bool autoPlay;
|
final bool autoPlay;
|
||||||
final bool enableInfiniteScroll;
|
final bool enableInfiniteScroll;
|
||||||
final Function()? onLastClick;
|
final Function()? onLastClick;
|
||||||
|
final Function(int index, CarouselPageChangedReason reason)? onPageChanged;
|
||||||
|
final CarouselSliderController? controller;
|
||||||
const CarouselSliderWidget({
|
const CarouselSliderWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.items,
|
required this.items,
|
||||||
|
|
@ -23,8 +24,13 @@ class CarouselSliderWidget<T> extends StatefulWidget {
|
||||||
this.onLastClick,
|
this.onLastClick,
|
||||||
this.autoPlay = true,
|
this.autoPlay = true,
|
||||||
this.enableInfiniteScroll = true,
|
this.enableInfiniteScroll = true,
|
||||||
|
this.enlargeCenterPage = true,
|
||||||
this.height = 200,
|
this.height = 200,
|
||||||
this.viewportFraction = 0.9,
|
this.viewportFraction = 0.9,
|
||||||
|
this.aspectRatio = 16 / 9,
|
||||||
|
this.withInds = true,
|
||||||
|
this.controller,
|
||||||
|
this.onPageChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -34,7 +40,8 @@ class CarouselSliderWidget<T> extends StatefulWidget {
|
||||||
|
|
||||||
class _CarouselSliderWidgetState<T> extends State<CarouselSliderWidget<T>> {
|
class _CarouselSliderWidgetState<T> extends State<CarouselSliderWidget<T>> {
|
||||||
int activeIndex = 0;
|
int activeIndex = 0;
|
||||||
final CarouselSliderController _controller = CarouselSliderController();
|
late final CarouselSliderController _controller =
|
||||||
|
widget.controller ?? CarouselSliderController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -45,61 +52,66 @@ class _CarouselSliderWidgetState<T> extends State<CarouselSliderWidget<T>> {
|
||||||
carouselController: _controller,
|
carouselController: _controller,
|
||||||
itemCount: widget.items.length,
|
itemCount: widget.items.length,
|
||||||
itemBuilder: (context, index, realIndex) {
|
itemBuilder: (context, index, realIndex) {
|
||||||
return widget.onPageBuilder(context, widget.items[index]);
|
return widget.onPageBuilder(context, widget.items[index], index);
|
||||||
},
|
},
|
||||||
options: CarouselOptions(
|
options: CarouselOptions(
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
autoPlay: widget.autoPlay,
|
autoPlay: widget.autoPlay,
|
||||||
enableInfiniteScroll: widget.enableInfiniteScroll,
|
enableInfiniteScroll: widget.enableInfiniteScroll,
|
||||||
enlargeCenterPage: true,
|
enlargeCenterPage: widget.enlargeCenterPage,
|
||||||
|
aspectRatio: widget.aspectRatio,
|
||||||
viewportFraction: widget.viewportFraction,
|
viewportFraction: widget.viewportFraction,
|
||||||
onPageChanged: (index, reason) =>
|
onPageChanged: (index, reason) {
|
||||||
setState(() => activeIndex = index),
|
widget.onPageChanged?.call(index, reason);
|
||||||
|
setState(() => activeIndex = index);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
if (widget.withInds || widget.withNavs)
|
||||||
padding: const EdgeInsets.all(16.0),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.all(16.0),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Expanded(child: Center(child: buildIndicator())),
|
children: [
|
||||||
if (widget.withNavs)
|
if (widget.withInds)
|
||||||
Expanded(
|
Expanded(child: Center(child: buildIndicator())),
|
||||||
child: Row(
|
if (widget.withNavs)
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
Expanded(
|
||||||
children: [
|
child: Row(
|
||||||
Opacity(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
opacity: activeIndex != 0 ? 1 : 0,
|
children: [
|
||||||
child: IconButton(
|
Opacity(
|
||||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
opacity: activeIndex != 0 ? 1 : 0,
|
||||||
onPressed: () => _controller.previousPage(),
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
|
onPressed: () => _controller.previousPage(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
Container(
|
padding: EdgeInsets.all(8),
|
||||||
padding: EdgeInsets.all(8),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
shape: BoxShape.circle,
|
||||||
shape: BoxShape.circle,
|
color: Theme.of(context).primaryColor),
|
||||||
color: Theme.of(context).primaryColor),
|
child: InkWell(
|
||||||
child: InkWell(
|
highlightColor: Theme.of(context).primaryColor,
|
||||||
highlightColor: Theme.of(context).primaryColor,
|
onTap: () {
|
||||||
onTap: () {
|
if (activeIndex == widget.items.length - 1) {
|
||||||
if (activeIndex == widget.items.length - 1) {
|
if (widget.onLastClick != null) {
|
||||||
if (widget.onLastClick != null) {
|
widget.onLastClick!();
|
||||||
widget.onLastClick!();
|
}
|
||||||
|
} else {
|
||||||
|
_controller.nextPage();
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
_controller.nextPage();
|
child: const Icon(Icons.arrow_forward_ios_rounded),
|
||||||
}
|
),
|
||||||
},
|
|
||||||
child: const Icon(Icons.arrow_forward_ios_rounded),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class DialogManager {
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
DialogManager({required this.context});
|
||||||
|
|
||||||
|
// Future _showDialog(Widget widget) async {
|
||||||
|
// await showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) {
|
||||||
|
// return Dialog(
|
||||||
|
// child: widget,
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import 'package:carousel_slider/carousel_controller.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
||||||
|
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
||||||
|
|
||||||
|
// TutorialOverlay
|
||||||
|
class ImageGallary extends ModalRoute<void> {
|
||||||
|
final List<String> urls;
|
||||||
|
final String initialUrl;
|
||||||
|
final CarouselSliderController carouselSliderController =
|
||||||
|
CarouselSliderController();
|
||||||
|
|
||||||
|
ImageGallary({required this.urls, required this.initialUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => Duration(milliseconds: 500);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get opaque => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get barrierDismissible => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get barrierColor => Colors.black.withAlpha(210);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barrierLabel => 'Images';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get maintainState => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(
|
||||||
|
BuildContext context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation,
|
||||||
|
) {
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: SafeArea(
|
||||||
|
child: _buildOverlayContent(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final ValueNotifier<String> initialImage = ValueNotifier(initialUrl);
|
||||||
|
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 =
|
||||||
|
(1 / (urls.length * (MediaQuery.sizeOf(context).width / 1300)));
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: MediaQuery.sizeOf(context).height / 2),
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: initialImage,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return InteractiveViewer(
|
||||||
|
boundaryMargin: EdgeInsets.all(80),
|
||||||
|
minScale: 0.5,
|
||||||
|
maxScale: 4,
|
||||||
|
panEnabled: true,
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio:
|
||||||
|
Responsive(context).isMobile() ? 1 / 1 : 16 / 9,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(32.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
if (details.primaryVelocity! < 0) {
|
||||||
|
// Swiped left
|
||||||
|
carouselSliderController.nextPage();
|
||||||
|
} else if (details.primaryVelocity! > 0) {
|
||||||
|
// Swiped right
|
||||||
|
carouselSliderController.previousPage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.network(
|
||||||
|
value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CarouselSliderWidget(
|
||||||
|
controller: carouselSliderController
|
||||||
|
..onReady.then(
|
||||||
|
(value) {
|
||||||
|
carouselSliderController
|
||||||
|
.jumpToPage(urls.indexOf(initialImage.value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
items: urls,
|
||||||
|
withInds: false,
|
||||||
|
autoPlay: false,
|
||||||
|
enableInfiniteScroll: false,
|
||||||
|
enlargeCenterPage: false,
|
||||||
|
viewportFraction: viewportFraction,
|
||||||
|
aspectRatio: 1 / 1,
|
||||||
|
height: 120,
|
||||||
|
onPageChanged: (index, reason) {
|
||||||
|
initialImage.value = urls[index];
|
||||||
|
},
|
||||||
|
onPageBuilder: (context, item, index) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft:
|
||||||
|
index == 0 ? Radius.circular(16) : Radius.zero,
|
||||||
|
bottomLeft:
|
||||||
|
index == 0 ? Radius.circular(16) : Radius.zero,
|
||||||
|
bottomRight: index == urls.length - 1
|
||||||
|
? Radius.circular(16)
|
||||||
|
: Radius.zero,
|
||||||
|
topRight: index == urls.length - 1
|
||||||
|
? Radius.circular(16)
|
||||||
|
: Radius.zero)),
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
carouselSliderController.animateToPage(index);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
child: Image.network(
|
||||||
|
item,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 32,
|
||||||
|
left: 32,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
child: Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation, Widget child) {
|
||||||
|
// You can add your own animations for the overlay content
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue