diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index 3a52931..fef02c5 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.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:proxibuy/presentation/providers/cubit/user_info_cubit.dart'; import 'package:proxibuy/presentation/ui/screens/auth/auth_page.dart'; diff --git a/lib/core/services/api/api_routes.dart b/lib/core/services/api/api_routes.dart index c5f05b9..f1e24bc 100644 --- a/lib/core/services/api/api_routes.dart +++ b/lib/core/services/api/api_routes.dart @@ -1,3 +1,4 @@ class ApiRoutes { static final userInfo = '/userinfo'; + static final category = '/category'; } diff --git a/lib/core/services/api/api_service.dart b/lib/core/services/api/api_service.dart index 9afa80d..d66b2c1 100644 --- a/lib/core/services/api/api_service.dart +++ b/lib/core/services/api/api_service.dart @@ -5,21 +5,19 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart'; final ApiService apiService = ApiService(); class ApiService { - final keycloak = 'https://keycloak.liara.run/'; - final clientID = 'frontend'; - final realm = 'lba'; + final baseUrl = 'https://lba-api.liara.run'; late Dio _dio; 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() { _dio = Dio( BaseOptions( - baseUrl: - '$keycloak/$realm/protocol/openid-connect', // 🔹 Set your API base URL + baseUrl: baseUrl, // 🔹 Set your API base URL connectTimeout: const Duration(seconds: 10), // Timeout settings receiveTimeout: const Duration(seconds: 10), headers: { @@ -30,6 +28,7 @@ class ApiService { )..interceptors.add(PrettyDioLogger( enabled: kDebugMode, )); + setAuthToken(''); } /// 🔹 Handle GET requests diff --git a/lib/core/services/api/response_model.dart b/lib/core/services/api/response_model.dart new file mode 100644 index 0000000..a6f5900 --- /dev/null +++ b/lib/core/services/api/response_model.dart @@ -0,0 +1,84 @@ +class ResponseModel { + T? data; + Meta? meta; + Links? links; + + ResponseModel({this.data, this.meta, this.links}); + + ResponseModel.fromJson( + Map 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 toJson(Map Function(T) toJsonT) { + final Map data = {}; + 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>? sortBy; + + Meta( + {this.itemsPerPage, + this.totalItems, + this.currentPage, + this.totalPages, + this.sortBy}); + + Meta.fromJson(Map json) { + itemsPerPage = json['itemsPerPage']; + totalItems = json['totalItems']; + currentPage = json['currentPage']; + totalPages = json['totalPages']; + if (json['sortBy'] != null) { + // sortBy = >[]; + // json['sortBy'].forEach((v) { + // sortBy!.addAll(v.toList()); + // }); + } + } + + Map toJson() { + final Map data = {}; + 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 json) { + current = json['current']; + } + + Map toJson() { + final Map data = {}; + data['current'] = current; + return data; + } +} diff --git a/lib/data/models/categories/categories_model.dart b/lib/data/models/categories/categories_model.dart new file mode 100644 index 0000000..795dcb5 --- /dev/null +++ b/lib/data/models/categories/categories_model.dart @@ -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 json) { + id = json['id']; + name = json['name']; + count = json['count']; + parentId = json['parent_id']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['count'] = count; + data['parent_id'] = parentId; + return data; + } +} diff --git a/lib/data/repositories/category_repository.dart b/lib/data/repositories/category_repository.dart new file mode 100644 index 0000000..84a508c --- /dev/null +++ b/lib/data/repositories/category_repository.dart @@ -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?> fetchAll() async { + try { + var response = await apiService.get(ApiRoutes.category); + print("Users: $response"); + final res = ResponseModel>.fromJson( + response, + (data) { + return (data as List) + .map((item) => CategoriesModel.fromJson(item)) + .toList(); + }, + ); + return res.data; + } catch (e) { + print("Error: $e"); + rethrow; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index b52d27b..d01890d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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/utils/my_custom_scroll_behavior.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/user_info_cubit.dart'; import 'package:proxibuy/presentation/ui/theme/theme.dart'; @@ -18,6 +19,7 @@ void main() async { runApp(MultiBlocProvider( providers: [ BlocProvider(create: (context) => ThemModeCubit()), + BlocProvider(create: (context) => CategoriesCubit()), BlocProvider( create: (context) => UserInfoCubit()..getUserInfo()), ], diff --git a/lib/presentation/providers/cubit/categories_cubit.dart b/lib/presentation/providers/cubit/categories_cubit.dart new file mode 100644 index 0000000..bda88b8 --- /dev/null +++ b/lib/presentation/providers/cubit/categories_cubit.dart @@ -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 { + CategoriesCubit() : super(CategoriesInitial()); + + void updateCategories(List newCategories) { + emit(state.copyWith(categories: newCategories)); + } + + Future 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())); + } + } +} diff --git a/lib/presentation/providers/cubit/categories_state.dart b/lib/presentation/providers/cubit/categories_state.dart new file mode 100644 index 0000000..9499310 --- /dev/null +++ b/lib/presentation/providers/cubit/categories_state.dart @@ -0,0 +1,39 @@ +part of 'categories_cubit.dart'; + +class CategoriesState { + final List categories; + final bool isLoading; + final String? errorMessage; + + const CategoriesState({ + this.categories = const [], + this.isLoading = false, + this.errorMessage, + }); + + CategoriesState copyWith({ + List? 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 categories) + : super(categories: categories); +} + +final class CategoriesError extends CategoriesState { + const CategoriesError(String errorMessage) + : super(errorMessage: errorMessage); +} diff --git a/lib/presentation/ui/screens/auth/auth_page.dart b/lib/presentation/ui/screens/auth/auth_page.dart index c342b65..a50ee27 100644 --- a/lib/presentation/ui/screens/auth/auth_page.dart +++ b/lib/presentation/ui/screens/auth/auth_page.dart @@ -214,7 +214,7 @@ class _AuthPageState extends State { onBoarding = false; }); }, - onPageBuilder: (context, item) { + onPageBuilder: (context, item, _) { return Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/presentation/ui/screens/home/home_page.dart b/lib/presentation/ui/screens/home/home_page.dart index 5e62f0c..7d4985c 100644 --- a/lib/presentation/ui/screens/home/home_page.dart +++ b/lib/presentation/ui/screens/home/home_page.dart @@ -7,9 +7,6 @@ import 'package:proxibuy/core/routes/app_router.dart'; import 'package:proxibuy/core/utils/empty_space.dart'; import 'package:proxibuy/data/models/screen_model.dart'; import 'package:proxibuy/presentation/providers/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/theme.dart'; diff --git a/lib/presentation/ui/screens/home/home_screen.dart b/lib/presentation/ui/screens/home/home_screen.dart index c60abee..a28c9d1 100644 --- a/lib/presentation/ui/screens/home/home_screen.dart +++ b/lib/presentation/ui/screens/home/home_screen.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/utils/empty_space.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/responsive.dart'; import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart'; @@ -18,9 +19,20 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { + final ScrollController _scrollController = ScrollController(); + @override void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().getAllCategories(); + }); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); } @override @@ -32,6 +44,7 @@ class _HomeScreenState extends State { Scaffold desktop(BuildContext context) { return Scaffold( body: SingleChildScrollView( + controller: _scrollController, physics: BouncingScrollPhysics(), child: Center( child: ConstrainedBox( @@ -40,36 +53,46 @@ class _HomeScreenState extends State { children: [ 12.h, titleDivider(context, title: 'what\'s on your mind?', top: 16), - SizedBox( - width: double.infinity, - height: 40, - child: ListView.builder( - itemCount: 20, - scrollDirection: Axis.horizontal, - shrinkWrap: true, - padding: EdgeInsets.symmetric(horizontal: 10), - physics: BouncingScrollPhysics(), - itemBuilder: (context, index) { - return Container( - width: 200, + BlocBuilder( + builder: (context, state) { + if (state is CategoriesLoaded) { + return SizedBox( + width: double.infinity, height: 40, - margin: EdgeInsets.symmetric(horizontal: 6), - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).colorScheme.surface, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.car_crash), - 8.w, - Text('Category') - ], + child: ListView.builder( + itemCount: state.categories.length, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 10), + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return Container( + width: 200, + height: 40, + margin: EdgeInsets.symmetric(horizontal: 6), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.car_crash), + 8.w, + Text(state.categories[index].name ?? '') + ], + ), + ); + }, ), ); - }, - ), + } else if (state is CategoriesError) { + return Text('Failed to load categories'); + } else { + return CircularProgressIndicator(); + } + }, ), Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -84,7 +107,7 @@ class _HomeScreenState extends State { items: ['1', '2', '3', '4'], height: 320, viewportFraction: 0.9, - onPageBuilder: (context, item) { + onPageBuilder: (context, item, _) { return ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( @@ -118,7 +141,7 @@ class _HomeScreenState extends State { items: ['1', '2', '3', '4'], height: 280, viewportFraction: 0.9, - onPageBuilder: (context, item) { + onPageBuilder: (context, item, _) { return ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( @@ -145,7 +168,7 @@ class _HomeScreenState extends State { items: ['1', '2', '3', '4'], height: 280, viewportFraction: 0.9, - onPageBuilder: (context, item) { + onPageBuilder: (context, item, _) { return ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( @@ -197,66 +220,73 @@ class _HomeScreenState extends State { 10.w ], ), - body: SingleChildScrollView( - physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - searchBar(context), - categories(context), - titleDivider(context, title: 'Top 10 Discount & Offers'), - CarouselSliderWidget( - items: ['1', '2', '3', '4'], - height: 180, - viewportFraction: 0.8, - 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', - fit: BoxFit.cover, - width: double.infinity, - ), - ); - }, - ), - flashSales(context, top: 12), - titleDivider(context, title: 'special discount'), - CarouselSliderWidget( - items: ['1', '2', '3', '4'], - height: 180, - viewportFraction: 0.8, - 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', - fit: BoxFit.cover, - width: double.infinity, - ), - ); - }, - ), - seasonalDiscounts(context, top: 12), - titleDivider(context, title: 'Crafting something for you'), - CarouselSliderWidget( - items: ['1', '2', '3', '4'], - height: 180, - viewportFraction: 0.8, - 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', - fit: BoxFit.cover, - width: double.infinity, - ), - ); - }, - ), - firstPurchaseDiscount(context), - 12.h - ], + body: RefreshIndicator( + onRefresh: () async { + context.read().getAllCategories(); + }, + child: SingleChildScrollView( + controller: _scrollController, + physics: + BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + searchBar(context), + categories(context), + titleDivider(context, title: 'Top 10 Discount & Offers'), + CarouselSliderWidget( + items: ['1', '2', '3', '4'], + height: 180, + viewportFraction: 0.8, + 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', + fit: BoxFit.cover, + width: double.infinity, + ), + ); + }, + ), + flashSales(context, top: 12), + titleDivider(context, title: 'special discount'), + CarouselSliderWidget( + items: ['1', '2', '3', '4'], + height: 180, + viewportFraction: 0.8, + 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', + fit: BoxFit.cover, + width: double.infinity, + ), + ); + }, + ), + seasonalDiscounts(context, top: 12), + titleDivider(context, title: 'Crafting something for you'), + CarouselSliderWidget( + items: ['1', '2', '3', '4'], + height: 180, + viewportFraction: 0.8, + 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', + fit: BoxFit.cover, + width: double.infinity, + ), + ); + }, + ), + firstPurchaseDiscount(context), + 12.h + ], + ), ), ), ); @@ -600,31 +630,41 @@ class _HomeScreenState extends State { return Column( children: [ titleDivider(context, title: 'what\'s on your mind?', top: 16), - SizedBox( - width: double.infinity, - height: 64, - child: ListView.builder( - itemCount: 20, - scrollDirection: Axis.horizontal, - shrinkWrap: true, - padding: EdgeInsets.symmetric(horizontal: 10), - physics: BouncingScrollPhysics(), - itemBuilder: (context, index) { - return Container( - width: 64, + BlocBuilder( + builder: (context, state) { + if (state is CategoriesLoaded) { + return SizedBox( + width: double.infinity, 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), + child: ListView.builder( + itemCount: state.categories.length, + scrollDirection: Axis.horizontal, + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 10), + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return Container( + width: 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(); + } + }, ), ], ); diff --git a/lib/presentation/ui/screens/product/product_page.dart b/lib/presentation/ui/screens/product/product_page.dart index 46635ae..8cee04e 100644 --- a/lib/presentation/ui/screens/product/product_page.dart +++ b/lib/presentation/ui/screens/product/product_page.dart @@ -1,9 +1,9 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:proxibuy/core/utils/empty_space.dart'; import 'package:proxibuy/presentation/ui/theme/colors.dart'; import 'package:proxibuy/presentation/ui/theme/responsive.dart'; +import 'package:proxibuy/presentation/ui/widgets/dialog/image_gallary.dart'; class ProductPage extends StatelessWidget { const ProductPage({super.key}); @@ -57,15 +57,30 @@ class ProductPage extends StatelessWidget { child: Column( children: [ Expanded( - 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, - )), + child: InkWell( + onTap: () { + Navigator.of(context).push(ImageGallary( + initialUrl: + '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: [ + '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: 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( @@ -81,19 +96,34 @@ class ProductPage extends StatelessWidget { shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { - return 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'), + return InkWell( + onTap: () { + Navigator.of(context).push(ImageGallary( + initialUrl: + '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', + 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: 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: [ Padding( padding: const EdgeInsets.only(bottom: 46), - 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, - )), + child: InkWell( + onTap: () { + Navigator.of(context).push(ImageGallary( + initialUrl: + '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: [ + '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( left: 16, @@ -176,17 +221,32 @@ class ProductPage extends StatelessWidget { shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { - return 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'), + return InkWell( + onTap: () { + Navigator.of(context).push(ImageGallary( + initialUrl: + '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', + 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: 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), child: Flex( direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Flexible( flex: 1, diff --git a/lib/presentation/ui/theme/responsive.dart b/lib/presentation/ui/theme/responsive.dart index 2fca6ad..5b0f001 100644 --- a/lib/presentation/ui/theme/responsive.dart +++ b/lib/presentation/ui/theme/responsive.dart @@ -40,9 +40,9 @@ class Responsive { } } - bool isMobile() => MediaQuery.sizeOf(context).width < 904; + bool isMobile() => MediaQuery.sizeOf(context).width < 768; bool isTablet() => - MediaQuery.sizeOf(context).width < 1280 && - MediaQuery.sizeOf(context).width >= 904; - bool isDesktop() => MediaQuery.sizeOf(context).width >= 1280; + MediaQuery.sizeOf(context).width < 1024 && + MediaQuery.sizeOf(context).width >= 768; + bool isDesktop() => MediaQuery.sizeOf(context).width >= 1024; } diff --git a/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart b/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart index 02fa72d..c4069cc 100644 --- a/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart +++ b/lib/presentation/ui/widgets/carousel/carousel_slider_widget.dart @@ -4,17 +4,18 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; class CarouselSliderWidget extends StatefulWidget { final List items; - final Widget Function( - BuildContext context, - T item, - ) onPageBuilder; + final Widget Function(BuildContext context, T item, int index) onPageBuilder; final bool withNavs; - final double height; + final bool withInds; + final double? height; final double viewportFraction; + final double aspectRatio; + final bool enlargeCenterPage; final bool autoPlay; final bool enableInfiniteScroll; final Function()? onLastClick; - + final Function(int index, CarouselPageChangedReason reason)? onPageChanged; + final CarouselSliderController? controller; const CarouselSliderWidget({ super.key, required this.items, @@ -23,8 +24,13 @@ class CarouselSliderWidget extends StatefulWidget { this.onLastClick, this.autoPlay = true, this.enableInfiniteScroll = true, + this.enlargeCenterPage = true, this.height = 200, this.viewportFraction = 0.9, + this.aspectRatio = 16 / 9, + this.withInds = true, + this.controller, + this.onPageChanged, }); @override @@ -34,7 +40,8 @@ class CarouselSliderWidget extends StatefulWidget { class _CarouselSliderWidgetState extends State> { int activeIndex = 0; - final CarouselSliderController _controller = CarouselSliderController(); + late final CarouselSliderController _controller = + widget.controller ?? CarouselSliderController(); @override Widget build(BuildContext context) { @@ -45,61 +52,66 @@ class _CarouselSliderWidgetState extends State> { carouselController: _controller, itemCount: widget.items.length, itemBuilder: (context, index, realIndex) { - return widget.onPageBuilder(context, widget.items[index]); + return widget.onPageBuilder(context, widget.items[index], index); }, options: CarouselOptions( height: widget.height, autoPlay: widget.autoPlay, enableInfiniteScroll: widget.enableInfiniteScroll, - enlargeCenterPage: true, + enlargeCenterPage: widget.enlargeCenterPage, + aspectRatio: widget.aspectRatio, viewportFraction: widget.viewportFraction, - onPageChanged: (index, reason) => - setState(() => activeIndex = index), + onPageChanged: (index, reason) { + widget.onPageChanged?.call(index, reason); + setState(() => activeIndex = index); + }, ), ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded(child: Center(child: buildIndicator())), - if (widget.withNavs) - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Opacity( - opacity: activeIndex != 0 ? 1 : 0, - child: IconButton( - icon: const Icon(Icons.arrow_back_ios_new_rounded), - onPressed: () => _controller.previousPage(), + if (widget.withInds || widget.withNavs) + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.withInds) + Expanded(child: Center(child: buildIndicator())), + if (widget.withNavs) + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Opacity( + opacity: activeIndex != 0 ? 1 : 0, + child: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () => _controller.previousPage(), + ), ), - ), - Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).primaryColor), - child: InkWell( - highlightColor: Theme.of(context).primaryColor, - onTap: () { - if (activeIndex == widget.items.length - 1) { - if (widget.onLastClick != null) { - widget.onLastClick!(); + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).primaryColor), + child: InkWell( + highlightColor: Theme.of(context).primaryColor, + onTap: () { + if (activeIndex == widget.items.length - 1) { + if (widget.onLastClick != null) { + widget.onLastClick!(); + } + } else { + _controller.nextPage(); } - } else { - _controller.nextPage(); - } - }, - child: const Icon(Icons.arrow_forward_ios_rounded), + }, + child: const Icon(Icons.arrow_forward_ios_rounded), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), - ), ], ); } diff --git a/lib/presentation/ui/widgets/dialog/dialog_manager.dart b/lib/presentation/ui/widgets/dialog/dialog_manager.dart new file mode 100644 index 0000000..c498efe --- /dev/null +++ b/lib/presentation/ui/widgets/dialog/dialog_manager.dart @@ -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, + // ); + // }, + // ); + // } +} diff --git a/lib/presentation/ui/widgets/dialog/image_gallary.dart b/lib/presentation/ui/widgets/dialog/image_gallary.dart new file mode 100644 index 0000000..c6b560c --- /dev/null +++ b/lib/presentation/ui/widgets/dialog/image_gallary.dart @@ -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 { + final List 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 animation, + Animation secondaryAnimation, + ) { + return Material( + type: MaterialType.transparency, + child: SafeArea( + child: _buildOverlayContent(context), + ), + ); + } + + late final ValueNotifier 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 animation, + Animation secondaryAnimation, Widget child) { + // You can add your own animations for the overlay content + return FadeTransition( + opacity: animation, + child: ScaleTransition( + scale: animation, + child: child, + ), + ); + } +}