create gallary slider and call categories api.

This commit is contained in:
OkaykOrhmn 2025-02-18 17:11:28 +03:30
parent 25b5fe1160
commit 5b993e5c4c
17 changed files with 738 additions and 218 deletions

View File

@ -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';

View File

@ -1,3 +1,4 @@
class ApiRoutes { class ApiRoutes {
static final userInfo = '/userinfo'; static final userInfo = '/userinfo';
static final category = '/category';
} }

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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()),
], ],

View File

@ -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()));
}
}
}

View File

@ -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);
}

View File

@ -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: [

View File

@ -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';

View File

@ -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();
}
},
), ),
], ],
); );

View File

@ -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,

View File

@ -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;
} }

View File

@ -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),
), ),
), ],
], ),
), ),
), ],
], ),
), ),
),
], ],
); );
} }

View File

@ -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,
// );
// },
// );
// }
}

View File

@ -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,
),
);
}
}