Update project

This commit is contained in:
2026-07-23 15:36:11 +05:30
parent 1197cfc161
commit 14fffa40c6
84 changed files with 20918 additions and 2761 deletions

View File

@@ -0,0 +1,120 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
/// Manages all scroll, cart-bar visibility, back-to-top, and
/// network-retry logic for StoreViewPage.
class StoreViewController extends GetxController {
// ── Scroll ──────────────────────────────────────────────
final ScrollController scrollController = ScrollController();
double scrollOffset = 0.0;
bool showBackToTop = false;
bool hideCartBar = false;
Timer? _cartTimer;
// ── Mini-cart ────────────────────────────────────────────
RxBool showMiniCart = false.obs;
// ── Search ───────────────────────────────────────────────
final TextEditingController searchController = TextEditingController();
// ── Misc ─────────────────────────────────────────────────
var selectedCategoryId = 0.obs;
@override
void onInit() {
super.onInit();
scrollController.addListener(_onScroll);
}
@override
void onClose() {
scrollController.removeListener(_onScroll);
scrollController.dispose();
searchController.dispose();
_cartTimer?.cancel();
super.onClose();
}
// ── Scroll handler (used by the ScrollController listener) ──
void _onScroll() {
scrollOffset = scrollController.offset;
showBackToTop = scrollController.offset > 300;
update(['scroll']);
}
// ── Notification-based handler (NestedScrollView body) ──────
bool handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification &&
notification.metrics.axis == Axis.vertical) {
final double pixels = notification.metrics.pixels;
final bool isScrollingUp = pixels < scrollOffset;
scrollOffset = pixels;
if (isScrollingUp && pixels > 300) {
showBackToTop = true;
} else if (pixels <= 300 || !isScrollingUp) {
showBackToTop = false;
}
// Hide cart bar immediately on scroll
if (!hideCartBar) {
hideCartBar = true;
showMiniCart.value = false;
update(['cartBar']);
}
// Re-show cart bar after scroll stops
_cartTimer?.cancel();
_cartTimer = Timer(const Duration(milliseconds: 250), () {
if (hideCartBar) {
hideCartBar = false;
update(['cartBar']);
}
});
update(['scroll']);
}
return false;
}
// ── Background colour based on scroll position ───────────────
Color backgroundColorFor(List<Color> colors) {
if (colors.isEmpty) return Colors.white;
return colors[(scrollOffset ~/ 300) % colors.length];
}
// ── Scroll to top ─────────────────────────────────────────────
void scrollToTop() {
scrollController.animateTo(
0,
duration: const Duration(milliseconds: 400),
curve: Curves.easeOut,
);
}
// ── Network retry ─────────────────────────────────────────────
Future<void> retryNetworkCall(Future<void> Function() onSuccess) async {
final connectivityResult = await Connectivity().checkConnectivity();
if (connectivityResult == ConnectivityResult.mobile ||
connectivityResult == ConnectivityResult.wifi) {
debugPrint('✅ Internet available, retrying...');
await onSuccess();
} else {
debugPrint('❌ Still no internet connection');
Get.snackbar(
'No Internet',
'Please check your connection and try again.',
backgroundColor: Colors.grey[200],
colorText: Colors.black,
snackPosition: SnackPosition.TOP,
);
}
}
}

View File

@@ -0,0 +1,454 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart';
import '../../../../constants/color_constants.dart';
import '../../../../constants/font_constants.dart';
import '../../../../controllers/account_controller/profile.dart';
import '../../../../controllers/cart_controller/cart.dart';
import '../../../../controllers/dashboard_controller/dashboard_controller.dart';
import '../../../../controllers/tenant_list/tenant_controller.dart';
import '../../../../service/firebase_analytics/analytics_service.dart';
import '../../../../view/dashboard_view/tenant_profile.dart';
import '../../../../view/product/tenant_products.dart';
import '../../../../widgets/text_widget.dart';
import '../controller/store_controller.dart';
import '../widgets/store_widgets.dart' hide ZoomIconButton;
class StoreViewPage extends StatefulWidget {
const StoreViewPage({super.key});
@override
State<StoreViewPage> createState() => _StoreViewPageState();
}
class _StoreViewPageState extends State<StoreViewPage> {
// ── Controllers ──────────────────────────────────────────────
final StoreViewController storeCtrl = Get.put(StoreViewController());
final TenantController tenantCtrl = Get.put(TenantController());
final CartController cartCtrl = Get.put(CartController());
bool status = true;
// ─────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.white,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
);
return SafeArea(
child: Scaffold(
body: GetBuilder<StoreViewController>(
id: 'scroll',
builder: (ctrl) {
return Stack(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
color: ctrl.backgroundColorFor(ColorConstants.bgColors),
child: NestedScrollViewPlus(
physics: const ClampingScrollPhysics(),
controller: ctrl.scrollController,
headerSliverBuilder: (context, _) => [
CupertinoSliverRefreshControl(
onRefresh: () async {
await tenantCtrl.loadTenants();
await Future.delayed(const Duration(milliseconds: 500));
},
),
],
body: NotificationListener<ScrollNotification>(
onNotification: ctrl.handleScrollNotification,
child: CustomScrollView(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 12)),
// ── Section header ──────────────────────────────
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverToBoxAdapter(
child: ReusableTextWidget(
text: "Near by Stores",
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: FontConstants.fontFamily,
color: Colors.black,
),
),
),
// ── Store list ──────────────────────────────────
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 0),
sliver: Obx(() {
// No internet
if (!tenantCtrl.isConnected.value) {
return SliverToBoxAdapter(
child: NoInternetWidget(
onRetry: () => ctrl.retryNetworkCall(
() async => await tenantCtrl.loadTenants(),
),
),
);
}
// Loading
if (tenantCtrl.isLoading.value) {
return const StoreListShimmer();
}
// Empty
if (tenantCtrl.tenants.isEmpty) {
return const SliverToBoxAdapter(
child: NoStoresFound(),
);
}
// Store list
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final item = tenantCtrl.tenants[index];
return ZoomOnTap(
onTap: () => _openStore(item),
child: _StoreCard(
item: item,
status: status,
onInfoTap: () =>
Get.to(StoreOverviewScreen()),
onCategoryArrowTap: () =>
_openStore(item),
onSubcategoryTap: (_) =>
_openStore(item),
),
);
},
childCount: tenantCtrl.tenants.length,
),
);
}),
),
const SliverToBoxAdapter(child: SizedBox(height: 220)),
SliverToBoxAdapter(
child: Image.asset(
'assets/images/nearle_copyrights.png'),
),
],
),
),
),
),
],
);
},
),
),
);
}
// ── Helpers ────────────────────────────────────────────────────
void _openStore(dynamic item) async {
Get.to(() => ProductsScreen(
tenantId: item.tenantid!,
locationId: item.locationid!,
categoryId: item.categoryid!,
tenantName: item.tenantname!,
locationname: item.locationname!,
tenantLocation: item.suburb!,
tenantImage: item.tenantimage!,
tenantloc: item.locationid!,
subCategoryName: '',
));
await AnalyticsService.logEvent(
'store_viewed',
parameters: {
'store_id': item.tenantid!,
'store_name': item.tenantname!,
'locatiom': item.locationname!,
},
);
}
}
// ─────────────────────────────────────────────────────────────────
// Private store card widget (lives here because it needs item data)
// ─────────────────────────────────────────────────────────────────
class _StoreCard extends StatelessWidget {
final dynamic item;
final bool status;
final VoidCallback onInfoTap;
final VoidCallback onCategoryArrowTap;
final void Function(dynamic subcategory) onSubcategoryTap;
const _StoreCard({
required this.item,
required this.status,
required this.onInfoTap,
required this.onCategoryArrowTap,
required this.onSubcategoryTap,
});
double _rs(BuildContext context, double size) =>
size * (MediaQuery.of(context).size.width / 390);
double _rh(BuildContext context, double size) =>
size * (MediaQuery.of(context).size.height / 844);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 12, top: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE1E5EA), width: 0.55),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.10),
spreadRadius: 0,
blurRadius: 20,
offset: const Offset(0, 6),
),
BoxShadow(
color: Colors.black.withOpacity(0.06),
spreadRadius: 0,
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Header row ──────────────────────────────────────
Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReusableTextWidget(
text: item.tenantname!,
color: Colors.black.withOpacity(0.7),
fontFamily: FontConstants.fontFamily,
fontSize: _rs(context, 23),
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Row(
children: [
Icon(Icons.location_on,
color: Colors.grey, size: _rs(context, 11)),
ReusableTextWidget(
text: item.locationname!,
color: Colors.black.withOpacity(0.7),
fontFamily: FontConstants.fontFamily,
fontSize: _rs(context, 10),
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
],
),
ZoomIconButton(
onTap: onInfoTap,
icon: Icons.info_outline,
size: 25,
color: Colors.black87,
),
],
),
),
// ── Body: closed or open ─────────────────────────────
status == false
? const ClosedStoreWidget()
: Column(
children: [
// Banner
Container(
margin: const EdgeInsets.symmetric(horizontal: 12),
height: _rh(context, 150),
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: (item.tenantbanner != null &&
item.tenantbanner!.isNotEmpty)
? Image.network(
item.tenantbanner!,
fit: BoxFit.fill,
errorBuilder: (_, __, ___) => Center(
child: Icon(
Icons.image_not_supported_outlined,
size: _rs(context, 40),
color: Colors.white),
),
)
: Image.network(
'https://img.freepik.com/free-psd/healthy-eating-lifestyle-banner-template_23-2149087275.jpg',
fit: BoxFit.cover,
width: double.infinity,
),
),
),
// Category row
Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ReusableTextWidget(
text: 'Category',
color: Colors.black.withOpacity(0.7),
fontFamily: FontConstants.fontFamily,
fontSize: _rs(context, 15),
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
IconButton(
onPressed: onCategoryArrowTap,
icon: Icon(
Icons.arrow_circle_right_outlined,
color: Colors.black.withOpacity(0.6),
size: _rs(context, 24),
),
),
],
),
),
// Subcategories
SizedBox(
height: _rh(context, 160),
child: (item.subcategories != null &&
item.subcategories!.isNotEmpty)
? ListView.builder(
scrollDirection: Axis.horizontal,
padding:
const EdgeInsets.symmetric(horizontal: 12),
itemCount: item.subcategories!.length,
itemBuilder: (context, index) {
final product = item.subcategories![index];
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => onSubcategoryTap(product),
child: Container(
margin: const EdgeInsets.only(
bottom: 14, right: 12, top: 2),
height: _rh(context, 180),
width: _rs(context, 120),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.black12, width: 0.20),
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.10),
spreadRadius: 0,
blurRadius: 7,
offset: const Offset(0, 3),
),
],
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
SizedBox(height: _rh(context, 12)),
ClipOval(
child: Image.network(
product.image ?? '',
width: _rs(context, 80),
height: _rs(context, 80),
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
Icon(
Icons.image_not_supported,
color: Colors.grey,
size: _rs(context, 30),
),
loadingBuilder:
(_, child, progress) {
if (progress == null)
return child;
return const Center(
child:
CircularProgressIndicator(
color: Colors.grey),
);
},
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8),
child: Center(
child: ReusableTextWidget(
text: product.subcatname!,
color: Colors.black
.withOpacity(0.7),
fontFamily:
FontConstants.fontFamily,
fontSize: _rs(context, 12),
fontWeight: FontWeight.bold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
SizedBox(height: _rh(context, 12)),
],
),
),
);
},
)
: const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'No products available',
style: TextStyle(color: Colors.grey),
),
),
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,425 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import 'package:shimmer/shimmer.dart';
import '../../../../constants/color_constants.dart';
import '../../../../constants/font_constants.dart';
import '../../../../widgets/text_widget.dart';
// ─────────────────────────────────────────────
// Zoom-on-tap wrapper
// ─────────────────────────────────────────────
class ZoomOnTap extends StatefulWidget {
final Widget child;
final VoidCallback onTap;
const ZoomOnTap({
super.key,
required this.child,
required this.onTap,
});
@override
State<ZoomOnTap> createState() => _ZoomOnTapState();
}
class _ZoomOnTapState extends State<ZoomOnTap> {
double _scale = 1.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _scale = 0.96),
onTapUp: (_) {
setState(() => _scale = 1.0);
widget.onTap();
},
onTapCancel: () => setState(() => _scale = 1.0),
child: AnimatedScale(
scale: _scale,
duration: const Duration(milliseconds: 120),
curve: Curves.easeOut,
child: widget.child,
),
);
}
}
// ─────────────────────────────────────────────
// Zoom icon button
// ─────────────────────────────────────────────
class ZoomIconButton extends StatefulWidget {
final VoidCallback onTap;
final IconData icon;
final double size;
final Color color;
const ZoomIconButton({
super.key,
required this.onTap,
required this.icon,
this.size = 24,
this.color = Colors.black87,
});
@override
State<ZoomIconButton> createState() => _ZoomIconButtonState();
}
class _ZoomIconButtonState extends State<ZoomIconButton> {
double _scale = 1.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _scale = 0.85),
onTapUp: (_) {
setState(() => _scale = 1.0);
widget.onTap();
},
onTapCancel: () => setState(() => _scale = 1.0),
child: AnimatedScale(
scale: _scale,
duration: const Duration(milliseconds: 120),
curve: Curves.easeOut,
child: Icon(widget.icon, size: widget.size, color: widget.color),
),
);
}
}
// ─────────────────────────────────────────────
// Search bar
// ─────────────────────────────────────────────
class StoreSearchBar extends StatelessWidget {
final TextEditingController controller;
final String hint;
final VoidCallback? onTap;
const StoreSearchBar({
super.key,
required this.controller,
this.hint = 'Search',
this.onTap,
});
@override
Widget build(BuildContext context) {
return Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Icon(Icons.search, color: Colors.grey.shade600),
const SizedBox(width: 10),
Expanded(
child: TextField(
controller: controller,
onTap: onTap,
decoration: InputDecoration(
hintText: hint,
border: InputBorder.none,
hintStyle: TextStyle(color: Colors.grey.shade500, fontSize: 14),
),
),
),
],
),
);
}
}
// ─────────────────────────────────────────────
// No internet widget
// ─────────────────────────────────────────────
class NoInternetWidget extends StatelessWidget {
final VoidCallback onRetry;
const NoInternetWidget({super.key, required this.onRetry});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Lottie.asset(
'assets/lotties/no_internet.json',
width: 200,
height: 200,
fit: BoxFit.contain,
),
const SizedBox(height: 16),
ReusableTextWidget(
text: 'No Internet Connection',
color: Colors.grey[700]!,
fontFamily: FontConstants.fontFamily,
fontSize: 18,
fontWeight: FontWeight.w600,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: onRetry,
style: ElevatedButton.styleFrom(
backgroundColor: ColorConstants.primaryColor,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Retry',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
// ─────────────────────────────────────────────
// No stores found
// ─────────────────────────────────────────────
class NoStoresFound extends StatelessWidget {
const NoStoresFound({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
Lottie.asset('assets/lotties/QR Code scan on phone.json'),
const SizedBox(height: 10),
ReusableTextWidget(
text: 'We don\'t deliver to this location yet. Try browsing nearby stores instead.',
textAlign: TextAlign.center,
maxLines: 2,
fontSize: 14,
),
],
);
}
}
// ─────────────────────────────────────────────
// Closed store UI
// ─────────────────────────────────────────────
class ClosedStoreWidget extends StatelessWidget {
const ClosedStoreWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Lottie.asset(
'assets/lotties/shop.json',
height: 140,
repeat: true,
animate: true,
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
Text(
'Store is currently closed',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black.withOpacity(0.7),
),
),
const SizedBox(height: 6),
Text(
'Please come back later',
style: TextStyle(
fontSize: 14,
color: Colors.black.withOpacity(0.7),
),
),
const SizedBox(height: 20),
],
),
);
}
}
// ─────────────────────────────────────────────
// List shimmer (loading placeholder)
// ─────────────────────────────────────────────
class StoreListShimmer extends StatelessWidget {
const StoreListShimmer({super.key});
@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 160,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(12),
),
),
const SizedBox(height: 12),
Container(
height: 16,
width: MediaQuery.of(context).size.width * 0.6,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(6),
),
),
const SizedBox(height: 8),
Container(
height: 14,
width: MediaQuery.of(context).size.width * 0.4,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(6),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: Container(
height: 36,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8),
),
),
),
const SizedBox(width: 10),
Expanded(
child: Container(
height: 36,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
],
),
),
);
},
childCount: 5,
),
);
}
}
// ─────────────────────────────────────────────
// Grid shimmer (subcategory loading placeholder)
// ─────────────────────────────────────────────
class SubcategoryGridShimmer extends StatelessWidget {
const SubcategoryGridShimmer({super.key});
@override
Widget build(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
double maxCrossAxisExtent = 250;
if (screenWidth > 1200) {
maxCrossAxisExtent = 300;
} else if (screenWidth > 800) {
maxCrossAxisExtent = 280;
}
return SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
child: Container(
height: 140,
width: 140,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Container(height: 20, width: 100, color: Colors.white),
),
Container(
height: 35,
width: double.infinity,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(8)),
),
),
],
),
),
);
},
childCount: 6,
),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
mainAxisExtent: 230,
),
);
}
}