diff --git a/lib/presentation/pages/home/widgets/feature.dart b/lib/presentation/pages/home/widgets/feature.dart index 1083032..d7b4f8c 100644 --- a/lib/presentation/pages/home/widgets/feature.dart +++ b/lib/presentation/pages/home/widgets/feature.dart @@ -81,7 +81,7 @@ class HomeFeature extends StatelessWidget { title: 'Inventaris', color: const Color(0xFF00BCD4), icon: LineIcons.archive, - onTap: () {}, + onTap: () => context.router.push(InventoryRoute()), ), HomeFeatureTile( title: 'Pelanggan', diff --git a/lib/presentation/pages/inventory/inventory_page.dart b/lib/presentation/pages/inventory/inventory_page.dart new file mode 100644 index 0000000..f0004e7 --- /dev/null +++ b/lib/presentation/pages/inventory/inventory_page.dart @@ -0,0 +1,556 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import 'widgets/appbar.dart'; +import 'widgets/ingredient_tile.dart'; +import 'widgets/product_tile.dart'; +import 'widgets/stat_card.dart'; +import 'widgets/tabbar_delegate.dart'; + +// Sample inventory data for products +class ProductItem { + final String id; + final String name; + final String category; + final int quantity; + final double price; + final String status; + final String image; + + ProductItem({ + required this.id, + required this.name, + required this.category, + required this.quantity, + required this.price, + required this.status, + required this.image, + }); +} + +// Sample inventory data for ingredients +class IngredientItem { + final String id; + final String name; + final String unit; + final double quantity; + final double minQuantity; + final String status; + final String image; + + IngredientItem({ + required this.id, + required this.name, + required this.unit, + required this.quantity, + required this.minQuantity, + required this.status, + required this.image, + }); +} + +// Custom SliverPersistentHeaderDelegate untuk TabBar + +@RoutePage() +class InventoryPage extends StatefulWidget { + const InventoryPage({super.key}); + + @override + State createState() => _InventoryPageState(); +} + +class _InventoryPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeAnimationController; + late AnimationController _slideAnimationController; + late AnimationController _rotationAnimationController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation rotationAnimation; + late TabController _tabController; + + final List productItems = [ + ProductItem( + id: '1', + name: 'Laptop Gaming ASUS ROG', + category: 'Elektronik', + quantity: 5, + price: 15000000, + status: 'available', + image: '💻', + ), + ProductItem( + id: '2', + name: 'Kemeja Formal Pria', + category: 'Fashion', + quantity: 25, + price: 250000, + status: 'available', + image: '👔', + ), + ProductItem( + id: '3', + name: 'Smartphone Samsung Galaxy', + category: 'Elektronik', + quantity: 12, + price: 8500000, + status: 'available', + image: '📱', + ), + ProductItem( + id: '4', + name: 'Tas Ransel Travel', + category: 'Fashion', + quantity: 8, + price: 350000, + status: 'low_stock', + image: '🎒', + ), + ProductItem( + id: '4', + name: 'Tas Ransel Travel', + category: 'Fashion', + quantity: 8, + price: 350000, + status: 'low_stock', + image: '🎒', + ), + ProductItem( + id: '4', + name: 'Tas Ransel Travel', + category: 'Fashion', + quantity: 8, + price: 350000, + status: 'low_stock', + image: '🎒', + ), + ]; + + final List ingredientItems = [ + IngredientItem( + id: '1', + name: 'Tepung Terigu', + unit: 'kg', + quantity: 50.5, + minQuantity: 10.0, + status: 'available', + image: '🌾', + ), + IngredientItem( + id: '2', + name: 'Gula Pasir', + unit: 'kg', + quantity: 2.5, + minQuantity: 5.0, + status: 'low_stock', + image: '🍬', + ), + IngredientItem( + id: '3', + name: 'Telur Ayam', + unit: 'butir', + quantity: 120, + minQuantity: 50, + status: 'available', + image: '🥚', + ), + IngredientItem( + id: '4', + name: 'Susu Segar', + unit: 'liter', + quantity: 0, + minQuantity: 10.0, + status: 'out_of_stock', + image: '🥛', + ), + IngredientItem( + id: '5', + name: 'Mentega', + unit: 'kg', + quantity: 15.2, + minQuantity: 5.0, + status: 'available', + image: '🧈', + ), + ]; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + + _fadeAnimationController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + _slideAnimationController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _rotationAnimationController = AnimationController( + duration: const Duration(seconds: 20), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _fadeAnimationController, + curve: Curves.easeInOut, + ), + ); + + _slideAnimation = + Tween(begin: const Offset(0.0, 0.3), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideAnimationController, + curve: Curves.easeOutBack, + ), + ); + + rotationAnimation = Tween( + begin: 0.0, + end: 2 * 3.14159, + ).animate(_rotationAnimationController); + + _fadeAnimationController.forward(); + _slideAnimationController.forward(); + _rotationAnimationController.repeat(); + } + + @override + void dispose() { + _fadeAnimationController.dispose(); + _slideAnimationController.dispose(); + _rotationAnimationController.dispose(); + _tabController.dispose(); + super.dispose(); + } + + Color getStatusColor(String status) { + switch (status) { + case 'available': + return AppColor.success; + case 'low_stock': + return AppColor.warning; + case 'out_of_stock': + return AppColor.error; + default: + return AppColor.textSecondary; + } + } + + String getStatusText(String status) { + switch (status) { + case 'available': + return 'Tersedia'; + case 'low_stock': + return 'Stok Rendah'; + case 'out_of_stock': + return 'Habis'; + default: + return 'Unknown'; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + _buildSliverAppBar(), + SliverPersistentHeader( + pinned: true, + delegate: InventorySliverTabBarDelegate( + tabBar: TabBar( + controller: _tabController, + indicator: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + indicatorSize: TabBarIndicatorSize.tab, + indicatorPadding: const EdgeInsets.all(6), + labelColor: AppColor.textWhite, + unselectedLabelColor: AppColor.textSecondary, + labelStyle: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 13, + ), + unselectedLabelStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 13, + ), + dividerColor: Colors.transparent, + splashFactory: NoSplash.splashFactory, + overlayColor: MaterialStateProperty.all( + Colors.transparent, + ), + tabs: [ + Tab( + height: 40, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: const Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.inventory_2_rounded, size: 16), + SizedBox(width: 6), + Text('Produk'), + ], + ), + ), + ), + Tab( + height: 40, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: const Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.restaurant_menu_rounded, size: 16), + SizedBox(width: 6), + Text('Bahan'), + ], + ), + ), + ), + ], + ), + ), + ), + ]; + }, + body: TabBarView( + controller: _tabController, + children: [_buildProductTab(), _buildIngredientTab()], + ), + ), + ), + ), + ); + } + + Widget _buildSliverAppBar() { + return SliverAppBar( + expandedHeight: 120, + floating: false, + pinned: true, + elevation: 0, + backgroundColor: AppColor.primary, + flexibleSpace: InventoryAppBar(rotationAnimation: rotationAnimation), + ); + } + + Widget _buildProductTab() { + return CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _buildProductStats()), + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.75, + ), + delegate: SliverChildBuilderDelegate( + (context, index) => + InventoryProductTile(item: productItems[index]), + childCount: productItems.length, + ), + ), + ), + ], + ); + } + + Widget _buildIngredientTab() { + return CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _buildIngredientStats()), + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => + InventoryIngredientTile(item: ingredientItems[index]), + childCount: ingredientItems.length, + ), + ), + ), + ], + ); + } + + Widget _buildProductStats() { + final totalProducts = productItems.length; + final availableProducts = productItems + .where((item) => item.status == 'available') + .length; + final lowStockProducts = productItems + .where((item) => item.status == 'low_stock') + .length; + + return Container( + margin: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: _buildStatCard( + 'Total Produk', + totalProducts.toString(), + Icons.inventory_2_rounded, + AppColor.primary, + '+12%', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Tersedia', + availableProducts.toString(), + Icons.check_circle_rounded, + AppColor.success, + '+5%', + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _buildStatCard( + 'Stok Rendah', + lowStockProducts.toString(), + Icons.warning_rounded, + AppColor.warning, + '-8%', + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container(), // Empty space for balance + ), + ], + ), + ], + ), + ); + } + + Widget _buildIngredientStats() { + final totalIngredients = ingredientItems.length; + final availableIngredients = ingredientItems + .where((item) => item.status == 'available') + .length; + final lowStockIngredients = ingredientItems + .where((item) => item.status == 'low_stock') + .length; + final outOfStockIngredients = ingredientItems + .where((item) => item.status == 'out_of_stock') + .length; + + return Container( + margin: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: _buildStatCard( + 'Total Bahan', + totalIngredients.toString(), + Icons.restaurant_menu_rounded, + AppColor.primary, + '+8%', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Tersedia', + availableIngredients.toString(), + Icons.check_circle_rounded, + AppColor.success, + '+15%', + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _buildStatCard( + 'Stok Kurang', + lowStockIngredients.toString(), + Icons.warning_rounded, + AppColor.warning, + '-3%', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Habis', + outOfStockIngredients.toString(), + Icons.error_rounded, + AppColor.error, + '+1', + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildStatCard( + String title, + String value, + IconData icon, + Color color, + String change, + ) { + return TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 800), + builder: (context, animationValue, child) { + return Transform.scale( + scale: animationValue, + child: InventoryStatCard( + title: title, + value: value, + icon: icon, + color: color, + change: change, + ), + ); + }, + ); + } +} diff --git a/lib/presentation/pages/inventory/widgets/appbar.dart b/lib/presentation/pages/inventory/widgets/appbar.dart new file mode 100644 index 0000000..6be4fe7 --- /dev/null +++ b/lib/presentation/pages/inventory/widgets/appbar.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +class InventoryAppBar extends StatelessWidget { + const InventoryAppBar({super.key, required this.rotationAnimation}); + + final Animation rotationAnimation; + + @override + Widget build(BuildContext context) { + return FlexibleSpaceBar( + titlePadding: const EdgeInsets.only(left: 50, bottom: 16), + title: Text( + 'Inventaris', + style: AppStyle.xl.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + ), + ), + background: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Stack( + children: [ + Positioned( + right: -20, + top: -20, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: rotationAnimation.value, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.1), + ), + ), + ); + }, + ), + ), + Positioned( + left: -30, + bottom: -30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.5, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.05), + ), + ), + ); + }, + ), + ), + Positioned( + right: 80, + bottom: 30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.2, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.textWhite.withOpacity(0.08), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/inventory/widgets/ingredient_tile.dart b/lib/presentation/pages/inventory/widgets/ingredient_tile.dart new file mode 100644 index 0000000..5a54464 --- /dev/null +++ b/lib/presentation/pages/inventory/widgets/ingredient_tile.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../components/spacer/spacer.dart'; +import '../inventory_page.dart'; + +class InventoryIngredientTile extends StatelessWidget { + final IngredientItem item; + const InventoryIngredientTile({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.primaryWithOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + gradient: LinearGradient(colors: AppColor.backgroundGradient), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text(item.image, style: const TextStyle(fontSize: 24)), + ), + ), + const SpaceWidth(16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SpaceHeight(4), + Text( + 'Stok: ${item.quantity} ${item.unit}', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + SpaceHeight(4), + Text( + 'Min: ${item.minQuantity} ${item.unit}', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: getStatusColor(item.status), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + getStatusText(item.status), + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + ), + ), + ), + ], + ), + ); + } + + Color getStatusColor(String status) { + switch (status) { + case 'available': + return AppColor.success; + case 'low_stock': + return AppColor.warning; + case 'out_of_stock': + return AppColor.error; + default: + return AppColor.textSecondary; + } + } + + String getStatusText(String status) { + switch (status) { + case 'available': + return 'Tersedia'; + case 'low_stock': + return 'Stok Rendah'; + case 'out_of_stock': + return 'Habis'; + default: + return 'Unknown'; + } + } +} diff --git a/lib/presentation/pages/inventory/widgets/product_tile.dart b/lib/presentation/pages/inventory/widgets/product_tile.dart new file mode 100644 index 0000000..bcfdd6e --- /dev/null +++ b/lib/presentation/pages/inventory/widgets/product_tile.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../components/spacer/spacer.dart'; +import '../inventory_page.dart'; + +class InventoryProductTile extends StatelessWidget { + final ProductItem item; + const InventoryProductTile({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.primary.withOpacity(0.08), width: 1), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.06), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image Container + Container( + height: 85, + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.backgroundGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Center( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.9), + borderRadius: BorderRadius.circular(12), + ), + child: Text(item.image, style: const TextStyle(fontSize: 32)), + ), + ), + ), + + // Content Container + Expanded( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Product Name + Text( + item.name, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + height: 1.2, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + + SpaceHeight(4), + + // Category + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + item.category, + style: AppStyle.xs.copyWith( + fontSize: 9, + fontWeight: FontWeight.w500, + color: AppColor.primary, + ), + ), + ), + + const Spacer(), + + // Price + Text( + 'Rp ${item.price}', + style: AppStyle.sm.copyWith( + fontSize: 13, + fontWeight: FontWeight.w700, + color: AppColor.primary, + ), + ), + + SpaceHeight(6), + + // Quantity & Status + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${item.quantity} pcs', + style: AppStyle.xs.copyWith( + fontSize: 11, + fontWeight: FontWeight.w500, + color: AppColor.textSecondary, + ), + ), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: getStatusColor(item.status), + shape: BoxShape.circle, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } + + Color getStatusColor(String status) { + switch (status) { + case 'available': + return AppColor.success; + case 'low_stock': + return AppColor.warning; + case 'out_of_stock': + return AppColor.error; + default: + return AppColor.textSecondary; + } + } +} diff --git a/lib/presentation/pages/inventory/widgets/stat_card.dart b/lib/presentation/pages/inventory/widgets/stat_card.dart new file mode 100644 index 0000000..08d5125 --- /dev/null +++ b/lib/presentation/pages/inventory/widgets/stat_card.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../components/spacer/spacer.dart'; + +class InventoryStatCard extends StatelessWidget { + final String title; + final String value; + final IconData icon; + final Color color; + final String change; + const InventoryStatCard({ + super.key, + required this.title, + required this.value, + required this.icon, + required this.color, + required this.change, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.1), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + border: Border.all(color: color.withOpacity(0.2), width: 1.5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [color.withOpacity(0.2), color.withOpacity(0.1)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: color, size: 24), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _getChangeColor(change).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + change, + style: AppStyle.sm.copyWith( + color: _getChangeColor(change), + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + SpaceHeight(16), + Text( + title, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + SpaceHeight(4), + Text( + value, + style: AppStyle.h3.copyWith( + color: color, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + Color _getChangeColor(String change) { + if (change.startsWith('+')) { + return AppColor.success; + } else if (change.startsWith('-')) { + return AppColor.error; + } else { + return AppColor.warning; + } + } +} diff --git a/lib/presentation/pages/inventory/widgets/tabbar_delegate.dart b/lib/presentation/pages/inventory/widgets/tabbar_delegate.dart new file mode 100644 index 0000000..22a92e3 --- /dev/null +++ b/lib/presentation/pages/inventory/widgets/tabbar_delegate.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +class InventorySliverTabBarDelegate extends SliverPersistentHeaderDelegate { + final TabBar tabBar; + + InventorySliverTabBarDelegate({required this.tabBar}); + + @override + double get minExtent => 60; + + @override + double get maxExtent => 60; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Container( + decoration: BoxDecoration( + color: AppColor.surface, + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Container( + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: AppColor.primary.withOpacity(0.1), + width: 1, + ), + ), + child: tabBar, + ), + ); + } + + @override + bool shouldRebuild(InventorySliverTabBarDelegate oldDelegate) { + return false; + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index b57d15f..b70c6ed 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -36,5 +36,8 @@ class AppRouter extends RootStackRouter { // Customer AutoRoute(page: CustomerRoute.page), + + // Inventory + AutoRoute(page: InventoryRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 15ddee1..ecc9d9b 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -10,40 +10,42 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:apskel_owner_flutter/presentation/pages/auth/login/login_page.dart' - as _i5; + as _i6; import 'package:apskel_owner_flutter/presentation/pages/customer/customer_page.dart' as _i1; import 'package:apskel_owner_flutter/presentation/pages/form/daily_task_form_page.dart' as _i2; import 'package:apskel_owner_flutter/presentation/pages/home/home_page.dart' as _i3; -import 'package:apskel_owner_flutter/presentation/pages/language/language_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/inventory/inventory_page.dart' as _i4; +import 'package:apskel_owner_flutter/presentation/pages/language/language_page.dart' + as _i5; import 'package:apskel_owner_flutter/presentation/pages/main/main_page.dart' - as _i6; -import 'package:apskel_owner_flutter/presentation/pages/product/product_page.dart' as _i7; -import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/product/product_page.dart' as _i8; -import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart' as _i9; -import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart' as _i10; -import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart' as _i11; -import 'package:apskel_owner_flutter/presentation/pages/transaction/transaction_page.dart' +import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart' as _i12; -import 'package:auto_route/auto_route.dart' as _i13; +import 'package:apskel_owner_flutter/presentation/pages/transaction/transaction_page.dart' + as _i13; +import 'package:auto_route/auto_route.dart' as _i14; /// generated route for /// [_i1.CustomerPage] -class CustomerRoute extends _i13.PageRouteInfo { - const CustomerRoute({List<_i13.PageRouteInfo>? children}) +class CustomerRoute extends _i14.PageRouteInfo { + const CustomerRoute({List<_i14.PageRouteInfo>? children}) : super(CustomerRoute.name, initialChildren: children); static const String name = 'CustomerRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { return const _i1.CustomerPage(); @@ -53,13 +55,13 @@ class CustomerRoute extends _i13.PageRouteInfo { /// generated route for /// [_i2.DailyTasksFormPage] -class DailyTasksFormRoute extends _i13.PageRouteInfo { - const DailyTasksFormRoute({List<_i13.PageRouteInfo>? children}) +class DailyTasksFormRoute extends _i14.PageRouteInfo { + const DailyTasksFormRoute({List<_i14.PageRouteInfo>? children}) : super(DailyTasksFormRoute.name, initialChildren: children); static const String name = 'DailyTasksFormRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { return const _i2.DailyTasksFormPage(); @@ -69,13 +71,13 @@ class DailyTasksFormRoute extends _i13.PageRouteInfo { /// generated route for /// [_i3.HomePage] -class HomeRoute extends _i13.PageRouteInfo { - const HomeRoute({List<_i13.PageRouteInfo>? children}) +class HomeRoute extends _i14.PageRouteInfo { + const HomeRoute({List<_i14.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { return const _i3.HomePage(); @@ -84,145 +86,161 @@ class HomeRoute extends _i13.PageRouteInfo { } /// generated route for -/// [_i4.LanguagePage] -class LanguageRoute extends _i13.PageRouteInfo { - const LanguageRoute({List<_i13.PageRouteInfo>? children}) +/// [_i4.InventoryPage] +class InventoryRoute extends _i14.PageRouteInfo { + const InventoryRoute({List<_i14.PageRouteInfo>? children}) + : super(InventoryRoute.name, initialChildren: children); + + static const String name = 'InventoryRoute'; + + static _i14.PageInfo page = _i14.PageInfo( + name, + builder: (data) { + return const _i4.InventoryPage(); + }, + ); +} + +/// generated route for +/// [_i5.LanguagePage] +class LanguageRoute extends _i14.PageRouteInfo { + const LanguageRoute({List<_i14.PageRouteInfo>? children}) : super(LanguageRoute.name, initialChildren: children); static const String name = 'LanguageRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i4.LanguagePage(); + return const _i5.LanguagePage(); }, ); } /// generated route for -/// [_i5.LoginPage] -class LoginRoute extends _i13.PageRouteInfo { - const LoginRoute({List<_i13.PageRouteInfo>? children}) +/// [_i6.LoginPage] +class LoginRoute extends _i14.PageRouteInfo { + const LoginRoute({List<_i14.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i5.LoginPage(); + return const _i6.LoginPage(); }, ); } /// generated route for -/// [_i6.MainPage] -class MainRoute extends _i13.PageRouteInfo { - const MainRoute({List<_i13.PageRouteInfo>? children}) +/// [_i7.MainPage] +class MainRoute extends _i14.PageRouteInfo { + const MainRoute({List<_i14.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i6.MainPage(); + return const _i7.MainPage(); }, ); } /// generated route for -/// [_i7.ProductPage] -class ProductRoute extends _i13.PageRouteInfo { - const ProductRoute({List<_i13.PageRouteInfo>? children}) +/// [_i8.ProductPage] +class ProductRoute extends _i14.PageRouteInfo { + const ProductRoute({List<_i14.PageRouteInfo>? children}) : super(ProductRoute.name, initialChildren: children); static const String name = 'ProductRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i7.ProductPage(); + return const _i8.ProductPage(); }, ); } /// generated route for -/// [_i8.ProfilePage] -class ProfileRoute extends _i13.PageRouteInfo { - const ProfileRoute({List<_i13.PageRouteInfo>? children}) +/// [_i9.ProfilePage] +class ProfileRoute extends _i14.PageRouteInfo { + const ProfileRoute({List<_i14.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i8.ProfilePage(); + return const _i9.ProfilePage(); }, ); } /// generated route for -/// [_i9.ReportPage] -class ReportRoute extends _i13.PageRouteInfo { - const ReportRoute({List<_i13.PageRouteInfo>? children}) +/// [_i10.ReportPage] +class ReportRoute extends _i14.PageRouteInfo { + const ReportRoute({List<_i14.PageRouteInfo>? children}) : super(ReportRoute.name, initialChildren: children); static const String name = 'ReportRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i9.ReportPage(); + return const _i10.ReportPage(); }, ); } /// generated route for -/// [_i10.SchedulePage] -class ScheduleRoute extends _i13.PageRouteInfo { - const ScheduleRoute({List<_i13.PageRouteInfo>? children}) +/// [_i11.SchedulePage] +class ScheduleRoute extends _i14.PageRouteInfo { + const ScheduleRoute({List<_i14.PageRouteInfo>? children}) : super(ScheduleRoute.name, initialChildren: children); static const String name = 'ScheduleRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i10.SchedulePage(); + return const _i11.SchedulePage(); }, ); } /// generated route for -/// [_i11.SplashPage] -class SplashRoute extends _i13.PageRouteInfo { - const SplashRoute({List<_i13.PageRouteInfo>? children}) +/// [_i12.SplashPage] +class SplashRoute extends _i14.PageRouteInfo { + const SplashRoute({List<_i14.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i11.SplashPage(); + return const _i12.SplashPage(); }, ); } /// generated route for -/// [_i12.TransactionPage] -class TransactionRoute extends _i13.PageRouteInfo { - const TransactionRoute({List<_i13.PageRouteInfo>? children}) +/// [_i13.TransactionPage] +class TransactionRoute extends _i14.PageRouteInfo { + const TransactionRoute({List<_i14.PageRouteInfo>? children}) : super(TransactionRoute.name, initialChildren: children); static const String name = 'TransactionRoute'; - static _i13.PageInfo page = _i13.PageInfo( + static _i14.PageInfo page = _i14.PageInfo( name, builder: (data) { - return const _i12.TransactionPage(); + return const _i13.TransactionPage(); }, ); }