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