587 lines
25 KiB
Dart
587 lines
25 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../utils/theme.dart';
|
|
|
|
class PosSellingScreen extends StatefulWidget {
|
|
final String shopName;
|
|
final int sessionId;
|
|
|
|
const PosSellingScreen({super.key, required this.shopName, required this.sessionId});
|
|
|
|
@override
|
|
State<PosSellingScreen> createState() => _PosSellingScreenState();
|
|
}
|
|
|
|
class _PosSellingScreenState extends State<PosSellingScreen> {
|
|
// Mock Data
|
|
final List<Map<String, dynamic>> _foodProducts = [
|
|
{'name': 'Bacon Burger', 'price': 12.0, 'image': 'https://plus.unsplash.com/premium_photo-1661331777080-459247d853e3?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Cheese Burger', 'price': 10.5, 'image': 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Pizza Margherita', 'price': 15.0, 'image': 'https://images.unsplash.com/photo-1513104890138-7c749659a591?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Pizza Veggie', 'price': 14.0, 'image': 'https://images.unsplash.com/photo-1604382354936-07c5d9983bd3?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Pasta Carbonara', 'price': 13.5, 'image': 'https://images.unsplash.com/photo-1612874742237-6526221588e3?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Club Sandwich', 'price': 9.0, 'image': 'https://images.unsplash.com/photo-1597255682855-667d4ccf044d?q=80&w=300&auto=format&fit=crop'},
|
|
];
|
|
|
|
final List<Map<String, dynamic>> _drinkProducts = [
|
|
{'name': 'Coca-Cola', 'price': 3.0, 'image': 'https://images.unsplash.com/photo-1622483767028-3f66f32aef97?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Water 500ml', 'price': 2.0, 'image': 'https://images.unsplash.com/photo-1616118132534-381148898bb6?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Espresso', 'price': 2.5, 'image': 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Green Tea', 'price': 3.5, 'image': 'https://images.unsplash.com/photo-1627435601361-ec25f5b1d0e5?q=80&w=300&auto=format&fit=crop'},
|
|
{'name': 'Milkshake', 'price': 5.0, 'image': 'https://images.unsplash.com/photo-1579954115563-e72bf1381629?q=80&w=300&auto=format&fit=crop'},
|
|
];
|
|
|
|
String _selectedCategory = 'Food'; // 'Food' or 'Drinks'
|
|
List<Map<String, dynamic>> _cartItems = [];
|
|
|
|
void _addToCart(Map<String, dynamic> product) {
|
|
setState(() {
|
|
final index = _cartItems.indexWhere((item) => item['name'] == product['name']);
|
|
if (index != -1) {
|
|
_cartItems[index]['qty'] += 1;
|
|
} else {
|
|
_cartItems.add({
|
|
'name': product['name'],
|
|
'price': product['price'],
|
|
'qty': 1,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void _removeFromCart(int index) {
|
|
setState(() {
|
|
_cartItems.removeAt(index);
|
|
});
|
|
}
|
|
|
|
void _decrementCartItem(int index) {
|
|
setState(() {
|
|
if (_cartItems[index]['qty'] > 1) {
|
|
_cartItems[index]['qty'] -= 1;
|
|
} else {
|
|
_cartItems.removeAt(index);
|
|
}
|
|
});
|
|
}
|
|
|
|
double get _totalAmount {
|
|
return _cartItems.fold(0.0, (sum, item) => sum + (item['price'] * item['qty']));
|
|
}
|
|
|
|
int get _totalItems {
|
|
return _cartItems.fold(0, (sum, item) => sum + (item['qty'] as int));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF0F4F7),
|
|
body: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
if (constraints.maxWidth > 700) {
|
|
return _buildWideLayout();
|
|
} else {
|
|
return _buildMobileLayout();
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildWideLayout() {
|
|
return Row(
|
|
children: [
|
|
// Left: Register/Cart
|
|
Expanded(
|
|
flex: 4,
|
|
child: _buildCartPanel(isMobile: false),
|
|
),
|
|
// Right: Products
|
|
Expanded(
|
|
flex: 6,
|
|
child: _buildProductPanel(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildMobileLayout() {
|
|
return Stack(
|
|
children: [
|
|
// Product Panel Full Screen
|
|
Column(
|
|
children: [
|
|
Expanded(child: _buildProductPanel()),
|
|
const SizedBox(height: 80), // Space for bottom bar
|
|
],
|
|
),
|
|
|
|
// Cart Summary Bottom Bar
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: _buildMobileBottomBar(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildMobileBottomBar() {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF2C3E50),
|
|
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2))],
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('$_totalItems Items', style: const TextStyle(color: Colors.white70, fontSize: 13)),
|
|
const SizedBox(height: 4),
|
|
Text('₹ ${_totalAmount.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20)),
|
|
],
|
|
),
|
|
const Spacer(),
|
|
ElevatedButton.icon(
|
|
onPressed: _showMobileCartModal,
|
|
icon: const Icon(Icons.shopping_cart_outlined),
|
|
label: const Text('View Cart'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppTheme.secondaryColor,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showMobileCartModal() {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (context) => Container(
|
|
height: MediaQuery.of(context).size.height * 0.85,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFF2C3E50),
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Handle
|
|
Container(width: 40, height: 4, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration(color: Colors.white24, borderRadius: BorderRadius.circular(2))),
|
|
Expanded(child: _buildCartPanel(isMobile: true)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCartPanel({required bool isMobile}) {
|
|
return Container(
|
|
color: const Color(0xFF2C3E50),
|
|
child: Column(
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
|
|
decoration: const BoxDecoration(
|
|
border: Border(bottom: BorderSide(color: Colors.white10)),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Current Order', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18)),
|
|
if (isMobile)
|
|
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.keyboard_arrow_down, color: Colors.white70))
|
|
else
|
|
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close, color: Colors.white70))
|
|
],
|
|
),
|
|
),
|
|
|
|
// Cart Items
|
|
Expanded(
|
|
child: _cartItems.isEmpty
|
|
? const Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.shopping_cart_outlined, color: Colors.white24, size: 48),
|
|
SizedBox(height: 16),
|
|
Text('Cart is empty', style: TextStyle(color: Colors.white54)),
|
|
]
|
|
)
|
|
)
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.all(12),
|
|
itemCount: _cartItems.length,
|
|
itemBuilder: (context, index) {
|
|
final item = _cartItems[index];
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
decoration: BoxDecoration(color: Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(12)),
|
|
child: ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
title: Text(item['name'], style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600)),
|
|
subtitle: Text('${item['qty']} x ₹${item['price']}', style: const TextStyle(color: Colors.white70)),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('₹${(item['price'] * item['qty']).toStringAsFixed(2)}', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
|
const SizedBox(width: 8),
|
|
IconButton(
|
|
icon: const Icon(Icons.remove_circle_outline, size: 22, color: Colors.orangeAccent),
|
|
tooltip: 'Reduce Quantity',
|
|
onPressed: () => _decrementCartItem(index),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.add_circle_outline, size: 22, color: Colors.greenAccent),
|
|
tooltip: 'Add Quantity',
|
|
onPressed: () => _addToCart(item),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Totals & Actions
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: const BoxDecoration(color: Color(0xFF233140)),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Total', style: TextStyle(color: Colors.white, fontSize: 18)),
|
|
Text('₹ ${_totalAmount.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () {},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFE91E63),
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text('Customer', style: TextStyle(color: Colors.white)),
|
|
)
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: _showPaymentModal,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF4CAF50),
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
child: const Text('Payment', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
|
)
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProductPanel() {
|
|
return Container(
|
|
color: const Color(0xFFF0F4F7),
|
|
child: Column(
|
|
children: [
|
|
// Top Bar
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), // Top padding for status bar if mobile
|
|
color: Colors.white,
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
// Back button here too for convenience? Or relying on Cart back
|
|
if (MediaQuery.of(context).size.width <= 700)
|
|
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.arrow_back)),
|
|
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: [
|
|
_buildCategoryTab('Food', Icons.lunch_dining, const Color(0xFFF8BBD0), const Color(0xFFE91E63)),
|
|
const SizedBox(width: 12),
|
|
_buildCategoryTab('Drinks', Icons.local_drink, const Color(0xFFC8E6C9), const Color(0xFF4CAF50)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Grid
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: GridView.builder(
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: MediaQuery.of(context).size.width > 900 ? 5 : (MediaQuery.of(context).size.width > 600 ? 4 : 2),
|
|
childAspectRatio: 0.8,
|
|
crossAxisSpacing: 16,
|
|
mainAxisSpacing: 16,
|
|
),
|
|
itemCount: _selectedCategory == 'Food' ? _foodProducts.length : _drinkProducts.length,
|
|
itemBuilder: (context, index) {
|
|
final product = _selectedCategory == 'Food' ? _foodProducts[index] : _drinkProducts[index];
|
|
return _buildProductCard(product);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCategoryTab(String title, IconData icon, Color bgColor, Color textColor) {
|
|
final bool isSelected = _selectedCategory == title;
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _selectedCategory = title),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: isSelected ? bgColor : Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: isSelected ? Border.all(color: textColor.withOpacity(0.5), width: 1.5) : null,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, color: isSelected ? textColor : Colors.grey[600], size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(title, style: TextStyle(color: isSelected ? textColor : Colors.grey[800], fontWeight: FontWeight.w700)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProductCard(Map<String, dynamic> product) {
|
|
return GestureDetector(
|
|
onTap: () => _addToCart(product),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 12, offset: const Offset(0, 4)),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Expanded(
|
|
child: ClipRRect(
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
|
child: Image.network(
|
|
product['image'],
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (ctx, err, stack) => Container(
|
|
color: Colors.grey[100],
|
|
child: const Icon(Icons.restaurant, color: Colors.grey, size: 30)
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(product['name'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Color(0xFF2D3748)), maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
const SizedBox(height: 6),
|
|
Text('₹ ${product['price']}', style: const TextStyle(color: Color(0xFF38A169), fontWeight: FontWeight.bold, fontSize: 15)),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showPaymentModal() {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (context) => Container(
|
|
height: MediaQuery.of(context).size.height * 0.9,
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
|
decoration: BoxDecoration(
|
|
border: Border(bottom: BorderSide(color: Colors.grey[200]!)),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Payment', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
|
IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)),
|
|
],
|
|
),
|
|
),
|
|
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
// Payment Methods (Left)
|
|
Expanded(
|
|
flex: 4,
|
|
child: Container(
|
|
color: const Color(0xFFF8F9FA),
|
|
child: ListView(
|
|
padding: const EdgeInsets.all(20),
|
|
children: [
|
|
_buildPaymentMethod('Cash', Icons.money, true),
|
|
const SizedBox(height: 10),
|
|
_buildPaymentMethod('Bank', Icons.credit_card, false),
|
|
const SizedBox(height: 10),
|
|
_buildPaymentMethod('Customer Account', Icons.person, false),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Payment Details & Numpad (Right)
|
|
Expanded(
|
|
flex: 6,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Total Display
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Remaining to Pay', style: TextStyle(color: Colors.grey[600], fontSize: 14, fontWeight: FontWeight.w500)),
|
|
const SizedBox(height: 8),
|
|
Text('₹ ${_totalAmount.toStringAsFixed(2)}', style: const TextStyle(fontSize: 42, fontWeight: FontWeight.bold, color: Color(0xFF2D3748))),
|
|
],
|
|
),
|
|
|
|
const Spacer(),
|
|
|
|
// Validate Button
|
|
SizedBox(
|
|
height: 56,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () {
|
|
Navigator.pop(context); // Close Payment
|
|
_processOrderSuccess();
|
|
},
|
|
icon: const Icon(Icons.check_circle_outline),
|
|
label: const Text('Validate Order', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF4CAF50),
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
elevation: 4,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentMethod(String name, IconData icon, bool selected) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: selected ? Colors.white : Colors.transparent,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: selected ? Border.all(color: AppTheme.primaryColor, width: 2) : Border.all(color: Colors.grey[300]!),
|
|
boxShadow: selected ? [BoxShadow(color: AppTheme.primaryColor.withOpacity(0.1), blurRadius: 10)] : [],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, color: selected ? AppTheme.primaryColor : Colors.grey[600]),
|
|
const SizedBox(width: 12),
|
|
Text(name, style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: selected ? AppTheme.primaryColor : Colors.grey[800],
|
|
fontSize: 16
|
|
)),
|
|
const Spacer(),
|
|
if (selected) const Icon(Icons.check_circle, color: AppTheme.primaryColor),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _processOrderSuccess() {
|
|
setState(() {
|
|
_cartItems.clear();
|
|
});
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(Icons.check_circle, color: Colors.green, size: 64),
|
|
const SizedBox(height: 16),
|
|
const Text('Order Validated!', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 8),
|
|
const Text('Receipt has been printed.', style: TextStyle(color: Colors.grey)),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Next Order', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|