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

View File

@ -1,3 +1,4 @@
class ApiRoutes {
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();
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

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/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<ThemModeCubit>(create: (context) => ThemModeCubit()),
BlocProvider<CategoriesCubit>(create: (context) => CategoriesCubit()),
BlocProvider<UserInfoCubit>(
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;
});
},
onPageBuilder: (context, item) {
onPageBuilder: (context, item, _) {
return Column(
mainAxisSize: MainAxisSize.min,
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/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';

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/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<HomeScreen> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<CategoriesCubit>().getAllCategories();
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
@ -32,6 +44,7 @@ class _HomeScreenState extends State<HomeScreen> {
Scaffold desktop(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: _scrollController,
physics: BouncingScrollPhysics(),
child: Center(
child: ConstrainedBox(
@ -40,11 +53,14 @@ class _HomeScreenState extends State<HomeScreen> {
children: [
12.h,
titleDivider(context, title: 'what\'s on your mind?', top: 16),
SizedBox(
BlocBuilder<CategoriesCubit, CategoriesState>(
builder: (context, state) {
if (state is CategoriesLoaded) {
return SizedBox(
width: double.infinity,
height: 40,
child: ListView.builder(
itemCount: 20,
itemCount: state.categories.length,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 10),
@ -64,12 +80,19 @@ class _HomeScreenState extends State<HomeScreen> {
children: [
Icon(Icons.car_crash),
8.w,
Text('Category')
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<HomeScreen> {
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<HomeScreen> {
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<HomeScreen> {
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,8 +220,14 @@ class _HomeScreenState extends State<HomeScreen> {
10.w
],
),
body: SingleChildScrollView(
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
body: RefreshIndicator(
onRefresh: () async {
context.read<CategoriesCubit>().getAllCategories();
},
child: SingleChildScrollView(
controller: _scrollController,
physics:
BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -209,7 +238,7 @@ class _HomeScreenState extends State<HomeScreen> {
items: ['1', '2', '3', '4'],
height: 180,
viewportFraction: 0.8,
onPageBuilder: (context, item) {
onPageBuilder: (context, item, _) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
@ -226,7 +255,7 @@ class _HomeScreenState extends State<HomeScreen> {
items: ['1', '2', '3', '4'],
height: 180,
viewportFraction: 0.8,
onPageBuilder: (context, item) {
onPageBuilder: (context, item, _) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
@ -243,7 +272,7 @@ class _HomeScreenState extends State<HomeScreen> {
items: ['1', '2', '3', '4'],
height: 180,
viewportFraction: 0.8,
onPageBuilder: (context, item) {
onPageBuilder: (context, item, _) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
@ -259,6 +288,7 @@ class _HomeScreenState extends State<HomeScreen> {
],
),
),
),
);
}
@ -600,11 +630,14 @@ class _HomeScreenState extends State<HomeScreen> {
return Column(
children: [
titleDivider(context, title: 'what\'s on your mind?', top: 16),
SizedBox(
BlocBuilder<CategoriesCubit, CategoriesState>(
builder: (context, state) {
if (state is CategoriesLoaded) {
return SizedBox(
width: double.infinity,
height: 64,
child: ListView.builder(
itemCount: 20,
itemCount: state.categories.length,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 10),
@ -625,6 +658,13 @@ class _HomeScreenState extends State<HomeScreen> {
);
},
),
);
} 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: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,6 +57,20 @@ class ProductPage extends StatelessWidget {
child: Column(
children: [
Expanded(
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(
@ -68,6 +82,7 @@ class ProductPage extends StatelessWidget {
)),
),
),
),
Padding(
padding:
const EdgeInsets.only(top: 32.0, bottom: 64),
@ -81,7 +96,21 @@ class ProductPage extends StatelessWidget {
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return Container(
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),
@ -94,6 +123,7 @@ class ProductPage extends StatelessWidget {
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,6 +147,20 @@ class ProductPage extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.only(bottom: 46),
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(
@ -125,6 +169,7 @@ class ProductPage extends StatelessWidget {
fit: BoxFit.cover,
)),
),
),
Positioned(
left: 16,
right: 16,
@ -176,7 +221,21 @@ class ProductPage extends StatelessWidget {
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return Container(
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(
@ -187,6 +246,7 @@ class ProductPage extends StatelessWidget {
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,

View File

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

View File

@ -4,17 +4,18 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class CarouselSliderWidget<T> extends StatefulWidget {
final List<T> 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<T> 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<T> extends StatefulWidget {
class _CarouselSliderWidgetState<T> extends State<CarouselSliderWidget<T>> {
int activeIndex = 0;
final CarouselSliderController _controller = CarouselSliderController();
late final CarouselSliderController _controller =
widget.controller ?? CarouselSliderController();
@override
Widget build(BuildContext context) {
@ -45,23 +52,28 @@ class _CarouselSliderWidgetState<T> extends State<CarouselSliderWidget<T>> {
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);
},
),
),
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(

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