Files
meezi/mobile/meezi_app/lib/features/cart/cart_screen.dart
T
soroush.asadi a85890f30a chore: Flutter mobile app, CI, and dev tooling
- mobile/: Flutter/Dart merchant mobile app skeleton
- .github/: GitHub Actions CI workflows
- .dockerignore: exclude host node_modules from build context
- .cursorrules: Cursor IDE project rules
- .claude/: Claude Code project settings and launch config

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:35:27 +03:30

125 lines
4.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/sync/sync_engine.dart';
import '../../core/utils/currency_utils.dart';
import 'cart_state.dart';
class CartScreen extends ConsumerStatefulWidget {
const CartScreen({super.key, required this.slug});
final String slug;
@override
ConsumerState<CartScreen> createState() => _CartScreenState();
}
class _CartScreenState extends ConsumerState<CartScreen> {
final _phoneController = TextEditingController();
final _nameController = TextEditingController();
bool _submitting = false;
@override
void dispose() {
_phoneController.dispose();
_nameController.dispose();
super.dispose();
}
Future<void> _checkout() async {
final cart = ref.read(cartProvider);
if (cart.lines.isEmpty) return;
setState(() => _submitting = true);
final api = ref.read(publicApiProvider);
final sync = SyncEngine();
try {
final result = await api.placeOrder(
widget.slug,
tableId: cart.tableId,
items: cart.lines.map((l) => l.toOrderJson()).toList(),
guestPhone: _phoneController.text.isEmpty ? null : _phoneController.text,
guestName: _nameController.text.isEmpty ? null : _nameController.text,
);
if (!mounted) return;
if (result != null) {
ref.read(cartProvider.notifier).clear();
final orderId = result['orderId'] as String;
context.go('/order/$orderId/track');
}
} catch (_) {
sync.enqueueAttendance(
action: 'guest-order',
cafeId: widget.slug,
employeeId: cart.tableId ?? '',
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('آفلاین ذخیره شد — پس از اتصال دوباره تلاش کنید')),
);
}
} finally {
if (mounted) setState(() => _submitting = false);
}
}
@override
Widget build(BuildContext context) {
final cart = ref.watch(cartProvider);
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(title: const Text('سبد خرید')),
body: cart.lines.isEmpty
? const Center(child: Text('سبد خالی است'))
: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(cart.cafeName ?? '', style: Theme.of(context).textTheme.titleLarge),
if (cart.tableNumber != null)
Text('میز ${cart.tableNumber}'),
const SizedBox(height: 16),
...cart.lines.map(
(line) => ListTile(
title: Text('${line.name} × ${line.quantity}'),
subtitle: Text(formatToman(line.lineTotal)),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => ref.read(cartProvider.notifier).removeItem(line.menuItemId),
),
),
),
const Divider(),
Text('جمع: ${formatToman(cart.subtotal)}', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 16),
TextField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'نام (اختیاری)'),
),
const SizedBox(height: 8),
TextField(
controller: _phoneController,
decoration: const InputDecoration(labelText: 'موبایل (اختیاری)'),
keyboardType: TextInputType.phone,
),
const SizedBox(height: 24),
FilledButton(
onPressed: _submitting ? null : _checkout,
child: _submitting
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('ثبت سفارش'),
),
],
),
),
);
}
}