create gallary slider and call categories api.
This commit is contained in:
parent
25b5fe1160
commit
5b993e5c4c
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_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';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
class ApiRoutes {
|
||||
static final userInfo = '/userinfo';
|
||||
static final category = '/category';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,19 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
|||
final ApiService apiService = ApiService();
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
class ResponseModel<T> {
|
||||
T? data;
|
||||
Meta? meta;
|
||||
Links? links;
|
||||
|
||||
ResponseModel({this.data, this.meta, this.links});
|
||||
|
||||
ResponseModel.fromJson(
|
||||
Map<String, dynamic> json, T Function(dynamic) fromJsonT) {
|
||||
data = json['data'] != null ? fromJsonT(json['data']) : null;
|
||||
meta = json['meta'] != null ? Meta.fromJson(json['meta']) : null;
|
||||
links = json['links'] != null ? Links.fromJson(json['links']) : null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson(Map<String, dynamic> Function(T) toJsonT) {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = toJsonT(this.data as T);
|
||||
}
|
||||
if (meta != null) {
|
||||
data['meta'] = meta!.toJson();
|
||||
}
|
||||
if (links != null) {
|
||||
data['links'] = links!.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Meta {
|
||||
int? itemsPerPage;
|
||||
int? totalItems;
|
||||
int? currentPage;
|
||||
int? totalPages;
|
||||
List<List<String>>? sortBy;
|
||||
|
||||
Meta(
|
||||
{this.itemsPerPage,
|
||||
this.totalItems,
|
||||
this.currentPage,
|
||||
this.totalPages,
|
||||
this.sortBy});
|
||||
|
||||
Meta.fromJson(Map<String, dynamic> json) {
|
||||
itemsPerPage = json['itemsPerPage'];
|
||||
totalItems = json['totalItems'];
|
||||
currentPage = json['currentPage'];
|
||||
totalPages = json['totalPages'];
|
||||
if (json['sortBy'] != null) {
|
||||
// sortBy = <List<String>>[];
|
||||
// json['sortBy'].forEach((v) {
|
||||
// sortBy!.addAll(v.toList());
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['itemsPerPage'] = itemsPerPage;
|
||||
data['totalItems'] = totalItems;
|
||||
data['currentPage'] = currentPage;
|
||||
data['totalPages'] = totalPages;
|
||||
if (sortBy != null) {
|
||||
data['sortBy'] = sortBy!.map((v) => v).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Links {
|
||||
String? current;
|
||||
|
||||
Links({this.current});
|
||||
|
||||
Links.fromJson(Map<String, dynamic> json) {
|
||||
current = json['current'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['current'] = current;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
class CategoriesModel {
|
||||
String? id;
|
||||
String? name;
|
||||
int? count;
|
||||
String? parentId;
|
||||
|
||||
CategoriesModel({this.id, this.name, this.count, this.parentId});
|
||||
|
||||
CategoriesModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
name = json['name'];
|
||||
count = json['count'];
|
||||
parentId = json['parent_id'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['name'] = name;
|
||||
data['count'] = count;
|
||||
data['parent_id'] = parentId;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:proxibuy/core/services/api/api_routes.dart';
|
||||
import 'package:proxibuy/core/services/api/api_service.dart';
|
||||
import 'package:proxibuy/core/services/api/response_model.dart';
|
||||
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||
|
||||
class CategoryRepository {
|
||||
static Future<List<CategoriesModel>?> fetchAll() async {
|
||||
try {
|
||||
var response = await apiService.get(ApiRoutes.category);
|
||||
print("Users: $response");
|
||||
final res = ResponseModel<List<CategoriesModel>>.fromJson(
|
||||
response,
|
||||
(data) {
|
||||
return (data as List)
|
||||
.map((item) => CategoriesModel.fromJson(item))
|
||||
.toList();
|
||||
},
|
||||
);
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
print("Error: $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
|||
import 'package:proxibuy/core/routes/app_router.dart';
|
||||
import 'package:proxibuy/core/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()),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:proxibuy/data/models/categories/categories_model.dart';
|
||||
import 'package:proxibuy/data/repositories/category_repository.dart';
|
||||
|
||||
part 'categories_state.dart';
|
||||
|
||||
class CategoriesCubit extends Cubit<CategoriesState> {
|
||||
CategoriesCubit() : super(CategoriesInitial());
|
||||
|
||||
void updateCategories(List<CategoriesModel> newCategories) {
|
||||
emit(state.copyWith(categories: newCategories));
|
||||
}
|
||||
|
||||
Future<void> getAllCategories() async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
final categories = await CategoryRepository.fetchAll();
|
||||
if (categories != null) {
|
||||
emit(CategoriesLoaded(categories));
|
||||
} else {
|
||||
emit(const CategoriesError('Failed to load categories'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CategoriesError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
part of 'categories_cubit.dart';
|
||||
|
||||
class CategoriesState {
|
||||
final List<CategoriesModel> categories;
|
||||
final bool isLoading;
|
||||
final String? errorMessage;
|
||||
|
||||
const CategoriesState({
|
||||
this.categories = const [],
|
||||
this.isLoading = false,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
CategoriesState copyWith({
|
||||
List<CategoriesModel>? categories,
|
||||
bool? isLoading,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return CategoriesState(
|
||||
categories: categories ?? this.categories,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class CategoriesInitial extends CategoriesState {}
|
||||
|
||||
final class CategoriesLoading extends CategoriesState {}
|
||||
|
||||
final class CategoriesLoaded extends CategoriesState {
|
||||
const CategoriesLoaded(List<CategoriesModel> categories)
|
||||
: super(categories: categories);
|
||||
}
|
||||
|
||||
final class CategoriesError extends CategoriesState {
|
||||
const CategoriesError(String errorMessage)
|
||||
: super(errorMessage: errorMessage);
|
||||
}
|
||||
|
|
@ -214,7 +214,7 @@ class _AuthPageState extends State<AuthPage> {
|
|||
onBoarding = false;
|
||||
});
|
||||
},
|
||||
onPageBuilder: (context, item) {
|
||||
onPageBuilder: (context, item, _) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class DialogManager {
|
||||
final BuildContext context;
|
||||
|
||||
DialogManager({required this.context});
|
||||
|
||||
// Future _showDialog(Widget widget) async {
|
||||
// await showDialog(
|
||||
// context: context,
|
||||
// builder: (context) {
|
||||
// return Dialog(
|
||||
// child: widget,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
import 'package:carousel_slider/carousel_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:proxibuy/presentation/ui/theme/responsive.dart';
|
||||
import 'package:proxibuy/presentation/ui/widgets/carousel/carousel_slider_widget.dart';
|
||||
|
||||
// TutorialOverlay
|
||||
class ImageGallary extends ModalRoute<void> {
|
||||
final List<String> urls;
|
||||
final String initialUrl;
|
||||
final CarouselSliderController carouselSliderController =
|
||||
CarouselSliderController();
|
||||
|
||||
ImageGallary({required this.urls, required this.initialUrl});
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => Duration(milliseconds: 500);
|
||||
|
||||
@override
|
||||
bool get opaque => false;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
Color get barrierColor => Colors.black.withAlpha(210);
|
||||
|
||||
@override
|
||||
String get barrierLabel => 'Images';
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Widget buildPage(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SafeArea(
|
||||
child: _buildOverlayContent(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
late final ValueNotifier<String> initialImage = ValueNotifier(initialUrl);
|
||||
Widget _buildOverlayContent(BuildContext context) {
|
||||
double x = 1;
|
||||
if (MediaQuery.sizeOf(context).width < 400) {
|
||||
x = 0.27;
|
||||
} else if (MediaQuery.sizeOf(context).width < 600) {
|
||||
x = 0.2;
|
||||
} else if (Responsive(context).isMobile()) {
|
||||
x = 0.5;
|
||||
} else if (Responsive(context).isTablet()) {
|
||||
x = 1;
|
||||
} else {
|
||||
x = 1.5;
|
||||
}
|
||||
double viewportFraction =
|
||||
(1 / (urls.length * (MediaQuery.sizeOf(context).width / 1300)));
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height / 2),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: initialImage,
|
||||
builder: (context, value, child) {
|
||||
return InteractiveViewer(
|
||||
boundaryMargin: EdgeInsets.all(80),
|
||||
minScale: 0.5,
|
||||
maxScale: 4,
|
||||
panEnabled: true,
|
||||
child: AspectRatio(
|
||||
aspectRatio:
|
||||
Responsive(context).isMobile() ? 1 / 1 : 16 / 9,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(32.0),
|
||||
child: GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.primaryVelocity! < 0) {
|
||||
// Swiped left
|
||||
carouselSliderController.nextPage();
|
||||
} else if (details.primaryVelocity! > 0) {
|
||||
// Swiped right
|
||||
carouselSliderController.previousPage();
|
||||
}
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
value,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
CarouselSliderWidget(
|
||||
controller: carouselSliderController
|
||||
..onReady.then(
|
||||
(value) {
|
||||
carouselSliderController
|
||||
.jumpToPage(urls.indexOf(initialImage.value));
|
||||
},
|
||||
),
|
||||
items: urls,
|
||||
withInds: false,
|
||||
autoPlay: false,
|
||||
enableInfiniteScroll: false,
|
||||
enlargeCenterPage: false,
|
||||
viewportFraction: viewportFraction,
|
||||
aspectRatio: 1 / 1,
|
||||
height: 120,
|
||||
onPageChanged: (index, reason) {
|
||||
initialImage.value = urls[index];
|
||||
},
|
||||
onPageBuilder: (context, item, index) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft:
|
||||
index == 0 ? Radius.circular(16) : Radius.zero,
|
||||
bottomLeft:
|
||||
index == 0 ? Radius.circular(16) : Radius.zero,
|
||||
bottomRight: index == urls.length - 1
|
||||
? Radius.circular(16)
|
||||
: Radius.zero,
|
||||
topRight: index == urls.length - 1
|
||||
? Radius.circular(16)
|
||||
: Radius.zero)),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
carouselSliderController.animateToPage(index);
|
||||
},
|
||||
child: SizedBox(
|
||||
child: Image.network(
|
||||
item,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: 32,
|
||||
left: 32,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Icon(Icons.arrow_back_ios_new_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
// You can add your own animations for the overlay content
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: ScaleTransition(
|
||||
scale: animation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue