diff --git a/lib/src/pages/home_page.dart b/lib/src/pages/home_page.dart new file mode 100644 index 0000000..1649fd1 --- /dev/null +++ b/lib/src/pages/home_page.dart @@ -0,0 +1,161 @@ +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/controllers/home_controller.dart'; +import 'package:mom_kitchen/src/controllers/cart_controller.dart'; +import 'package:mom_kitchen/src/standards/page_standards.dart'; +import 'package:mom_kitchen/src/widgets/product_card.dart'; +import 'package:mom_kitchen/src/widgets/states/empty_state.dart'; +import 'package:mom_kitchen/src/widgets/states/error_state.dart'; +import 'package:mom_kitchen/src/widgets/loading_indicator.dart'; +import 'package:mom_kitchen/src/widgets/responsive_grid.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + final homeController = Get.put(HomeController()); + final cartController = Get.put(CartController()); + final standards = PageStandards.of(context); + + return CupertinoPageScaffold( + backgroundColor: standards.backgroundColor, + child: SafeArea( + child: Obx(() { + if (homeController.isLoading.value) { + return const LoadingIndicator(); + } + + if (homeController.errorMessage.value.isNotEmpty) { + return ErrorState( + message: homeController.errorMessage.value, + onRetry: () => homeController.loadProducts(), + ); + } + + return Column( + children: [ + _buildSearchBar(homeController, standards), + _buildCategoryFilter(homeController, standards), + Expanded( + child: homeController.filteredProducts.isEmpty + ? const EmptyState( + emoji: '🔍', + title: 'No products found', + message: 'Try a different search or category', + ) + : _buildProductGrid( + homeController, + cartController, + standards, + ), + ), + ], + ); + }), + ), + ); + } + + Widget _buildSearchBar(HomeController controller, PageStandards standards) { + return Padding( + padding: standards.scaledPadding(EdgeInsets.all(16)), + child: Container( + decoration: BoxDecoration( + color: standards.backgroundColor, + borderRadius: BorderRadius.circular(standards.scaledRadius(12)), + border: Border.all( + color: standards.textColor.withOpacity(0.1), + ), + ), + child: CupertinoSearchTextField( + placeholder: 'Search products...', + style: TextStyle(color: standards.textColor), + placeholderStyle: TextStyle( + color: standards.textColor.withOpacity(0.5), + ), + decoration: null, + onChanged: controller.search, + ), + ), + ); + } + + Widget _buildCategoryFilter(HomeController controller, PageStandards standards) { + return Obx(() => Container( + height: standards.scaledHeight(44), + padding: EdgeInsets.symmetric(horizontal: standards.scaledWidth(16)), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.categories.length + 1, + itemBuilder: (context, index) { + final isSelected = index == 0 + ? controller.selectedCategory.value.isEmpty + : controller.selectedCategory.value == + controller.categories[index - 1]; + + return Padding( + padding: EdgeInsets.only(right: standards.scaledWidth(8)), + child: GestureDetector( + onTap: () { + if (index == 0) { + controller.selectCategory(''); + } else { + controller.selectCategory(controller.categories[index - 1]); + } + }, + child: Container( + padding: standards.scaledPadding( + EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + decoration: BoxDecoration( + color: isSelected + ? standards.primaryColor + : standards.backgroundColor, + borderRadius: BorderRadius.circular( + standards.scaledRadius(20), + ), + border: Border.all( + color: isSelected + ? standards.primaryColor + : standards.textColor.withOpacity(0.2), + ), + ), + child: Text( + index == 0 ? 'All' : controller.categories[index - 1], + style: standards.textStyle.copyWith( + color: isSelected + ? CupertinoColors.white + : standards.textColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ); + }, + ), + )); + } + + Widget _buildProductGrid( + HomeController homeController, + CartController cartController, + PageStandards standards, + ) { + return ResponsiveGrid( + crossAxisCount: 2, + crossAxisCountMedium: 3, + crossAxisCountLarge: 4, + childAspectRatio: 0.75, + spacing: 12, + runSpacing: 12, + children: homeController.filteredProducts + .map((product) => ProductCard( + product: product, + onAddToCart: () => cartController.addProduct(product), + )) + .toList(), + ); + } +} diff --git a/lib/src/widgets/loading_indicator.dart b/lib/src/widgets/loading_indicator.dart new file mode 100644 index 0000000..3ef914e --- /dev/null +++ b/lib/src/widgets/loading_indicator.dart @@ -0,0 +1,43 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mom_kitchen/src/standards/page_standards.dart'; + +class LoadingIndicator extends StatelessWidget { + final String? message; + final double size; + final Color? color; + + const LoadingIndicator({ + super.key, + this.message, + this.size = 32, + this.color, + }); + + @override + Widget build(BuildContext context) { + final standards = PageStandards.of(context); + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoActivityIndicator( + radius: standards.scaledRadius(size / 2), + color: color ?? standards.primaryColor, + ), + if (message != null) + Padding( + padding: EdgeInsets.only(top: standards.scaledHeight(16)), + child: Text( + message!, + style: standards.textStyle.copyWith( + color: standards.textColor.withOpacity(0.6), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/widgets/product_card.dart b/lib/src/widgets/product_card.dart new file mode 100644 index 0000000..17cce97 --- /dev/null +++ b/lib/src/widgets/product_card.dart @@ -0,0 +1,114 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mom_kitchen/src/standards/page_standards.dart'; +import 'package:mom_kitchen/src/controllers/home_controller.dart'; + +class ProductCard extends StatelessWidget { + final ProductModel product; + final VoidCallback? onTap; + final VoidCallback? onAddToCart; + + const ProductCard({ + super.key, + required this.product, + this.onTap, + this.onAddToCart, + }); + + @override + Widget build(BuildContext context) { + final standards = PageStandards.of(context); + + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + color: standards.backgroundColor, + borderRadius: BorderRadius.circular(standards.scaledRadius(16)), + boxShadow: [ + BoxShadow( + color: standards.textColor.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImage(standards), + _buildInfo(standards), + ], + ), + ), + ); + } + + Widget _buildImage(PageStandards standards) { + return Expanded( + child: Container( + decoration: BoxDecoration( + color: standards.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.vertical( + top: Radius.circular(standards.scaledRadius(16)), + ), + ), + child: Center( + child: Text( + product.image, + style: TextStyle(fontSize: standards.scaledFontSize(48)), + ), + ), + ), + ); + } + + Widget _buildInfo(PageStandards standards) { + return Padding( + padding: standards.scaledPadding(EdgeInsets.all(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + style: standards.textStyle.copyWith( + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: standards.scaledHeight(4)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '\$${product.price.toStringAsFixed(2)}', + style: standards.primaryTextStyle.copyWith( + fontWeight: FontWeight.bold, + ), + ), + if (onAddToCart != null) + GestureDetector( + onTap: onAddToCart, + child: Container( + padding: standards.scaledPadding(EdgeInsets.all(6)), + decoration: BoxDecoration( + color: standards.primaryColor, + borderRadius: BorderRadius.circular( + standards.scaledRadius(8), + ), + ), + child: Icon( + CupertinoIcons.plus, + color: CupertinoColors.white, + size: standards.fontSize, + ), + ), + ), + ], + ), + ], + ), + ); + } +}