Skip to content

Commit 4fd3539

Browse files
author
Dang
committed
update orderlogic
1 parent e30f7ff commit 4fd3539

File tree

13 files changed

+246
-34
lines changed

13 files changed

+246
-34
lines changed

e_shoppe/lib/features/cart/cart_page.dart

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:e_shoppe/features/cart/bloc/cart_bloc.dart';
66
import 'package:e_shoppe/shared/utils/formatter.dart';
77
import 'package:e_shoppe/theme/app_theme.dart';
88
import 'package:e_shoppe/features/checkout/checkout_page.dart';
9+
import 'package:e_shoppe/shared/responsive.dart';
910

1011
class CartPage extends StatelessWidget {
1112
const CartPage({super.key});
@@ -16,30 +17,35 @@ class CartPage extends StatelessWidget {
1617
appBar: AppBar(title: const Text('Giỏ hàng')),
1718
body: BlocBuilder<CartBloc, CartState>(
1819
builder: (context, state) {
20+
// Build main content scroll list.
21+
Widget content;
1922
if (state.items.isEmpty) {
20-
return const Center(child: Text('Cart is empty'));
23+
content = const Center(child: Text('Cart is empty'));
24+
} else {
25+
content = ListView.separated(
26+
padding: const EdgeInsets.only(
27+
bottom: kBottomNavigationBarHeight + 60),
28+
// Extra padding so last item isn't hidden under summary & nav.
29+
itemCount: state.items.length,
30+
separatorBuilder: (_, __) => const Divider(height: 1),
31+
itemBuilder: (context, index) {
32+
final item = state.items[index];
33+
return _CartListTile(item: item);
34+
},
35+
);
2136
}
22-
return ListView.separated(
23-
itemCount: state.items.length,
24-
separatorBuilder: (_, __) => const Divider(height: 1),
25-
itemBuilder: (context, index) {
26-
final item = state.items[index];
27-
return _CartListTile(item: item);
28-
},
29-
);
30-
},
31-
),
32-
bottomNavigationBar: BlocBuilder<CartBloc, CartState>(
33-
builder: (context, state) {
34-
return Container(
37+
38+
// Summary bar that stays just above the app-wide bottom nav.
39+
final summaryBar = Container(
3540
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
3641
color: AppColors.white,
3742
child: Row(
3843
children: [
3944
Expanded(
40-
child: Text('Tổng: ${formatCurrency(state.total)}',
41-
style: const TextStyle(
42-
fontSize: 16, fontWeight: FontWeight.bold))),
45+
child: Text('Tổng: ${formatCurrency(state.total)}',
46+
style: const TextStyle(
47+
fontSize: 16, fontWeight: FontWeight.bold)),
48+
),
4349
ElevatedButton(
4450
onPressed: state.items.isNotEmpty
4551
? () {
@@ -54,6 +60,20 @@ class CartPage extends StatelessWidget {
5460
],
5561
),
5662
);
63+
64+
return Stack(
65+
children: [
66+
content,
67+
Positioned(
68+
left: 0,
69+
right: 0,
70+
bottom: Responsive.isMobile(context)
71+
? kBottomNavigationBarHeight
72+
: 0,
73+
child: summaryBar,
74+
),
75+
],
76+
);
5777
},
5878
),
5979
);

e_shoppe/lib/features/checkout/payment_page.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import 'package:e_shoppe/shared/widgets/blue_header.dart';
66
import 'package:e_shoppe/shared/widgets/section_card.dart';
77
import 'package:e_shoppe/theme/app_theme.dart';
88
import 'package:e_shoppe/features/cart/bloc/cart_bloc.dart';
9-
import 'package:e_shoppe/features/checkout/order_success_page.dart';
109
import 'package:e_shoppe/shared/responsive.dart';
1110
import 'package:e_shoppe/features/order/riverpod/order_draft_provider.dart';
1211
import 'package:e_shoppe/services/api_client.dart';
1312
import 'package:e_shoppe/data/models/user.dart';
13+
import 'package:e_shoppe/features/home/home_shell.dart';
1414

1515
class PaymentPage extends ConsumerStatefulWidget {
1616
const PaymentPage({super.key});
@@ -118,9 +118,8 @@ class _PaymentPageState extends ConsumerState<PaymentPage> {
118118
ref.read(orderDraftProvider.notifier).clear();
119119

120120
navigator.pushAndRemoveUntil(
121-
MaterialPageRoute(
122-
builder: (_) => const OrderSuccessPage()),
123-
(route) => route.isFirst,
121+
MaterialPageRoute(builder: (_) => const HomeShell()),
122+
(_) => false,
124123
);
125124
} catch (e) {
126125
if (!mounted) return;

e_shoppe/lib/features/home/home_shell.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3-
import 'package:e_shoppe/shared/responsive.dart';
43
import 'package:e_shoppe/features/search/search_page.dart';
54
import 'package:e_shoppe/features/cart/cart_page.dart';
65
import 'package:e_shoppe/features/profile/profile_page.dart';
76
import 'package:e_shoppe/features/cart/bloc/cart_bloc.dart';
87
import 'package:e_shoppe/features/order/order_list_page.dart';
8+
import 'package:e_shoppe/theme/app_theme.dart';
99

1010
class HomeShell extends StatefulWidget {
1111
const HomeShell({super.key});
@@ -71,7 +71,10 @@ class _HomeShellState extends State<HomeShell> {
7171
icon: Icon(Icons.person), label: 'Profile'),
7272
];
7373

74-
if (Responsive.isTablet(context) || Responsive.isDesktop(context)) {
74+
// Use side NavigationRail only on sufficiently wide screens.
75+
final showRail = MediaQuery.of(context).size.width >= 700;
76+
77+
if (showRail) {
7578
// Side rail
7679
return Scaffold(
7780
body: Row(
@@ -105,6 +108,12 @@ class _HomeShellState extends State<HomeShell> {
105108
currentIndex: _index,
106109
onTap: _onTap,
107110
items: navItems,
111+
backgroundColor: AppColors.white,
112+
type: BottomNavigationBarType.fixed,
113+
selectedItemColor: AppColors.cyan,
114+
unselectedItemColor: AppColors.darkGray,
115+
showUnselectedLabels: true,
116+
elevation: 4,
108117
),
109118
);
110119
});

e_shoppe/lib/features/order/order_confirm_page.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:e_shoppe/data/models/cart_item.dart';
77
import 'package:flutter_bloc/flutter_bloc.dart';
88
import 'package:e_shoppe/features/auth/bloc/auth_bloc.dart';
99
import 'package:e_shoppe/data/models/user.dart';
10+
import 'package:e_shoppe/features/home/home_shell.dart';
1011

1112
class OrderConfirmPage extends ConsumerWidget {
1213
const OrderConfirmPage({super.key});
@@ -106,9 +107,9 @@ class OrderConfirmPage extends ConsumerWidget {
106107
ref.read(orderDraftProvider.notifier).clear();
107108

108109
if (!context.mounted) return;
109-
Navigator.of(context).pushNamedAndRemoveUntil(
110-
'/order/success',
111-
(route) => route.isFirst,
110+
Navigator.of(context).pushAndRemoveUntil(
111+
MaterialPageRoute(builder: (_) => const HomeShell()),
112+
(_) => false,
112113
);
113114
} catch (e) {
114115
// ensure loading dialog is closed

e_shoppe/lib/features/order/product_select_page.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,16 @@ class _ProductSelectPageState extends ConsumerState<ProductSelectPage> {
100100
onPressed: () {
101101
final item = CartItem(product: p, quantity: 1);
102102
ref.read(orderDraftProvider.notifier).addItem(item);
103-
ScaffoldMessenger.of(context).showSnackBar(
104-
const SnackBar(
105-
content: Text('Đã thêm vào đơn hàng')),
106-
);
103+
ScaffoldMessenger.of(context)
104+
..hideCurrentSnackBar()
105+
..showSnackBar(
106+
const SnackBar(
107+
content: Text('Đã thêm vào đơn hàng'),
108+
behavior: SnackBarBehavior.floating,
109+
margin: EdgeInsets.fromLTRB(16, 0, 16, 80),
110+
duration: Duration(seconds: 2),
111+
),
112+
);
107113
},
108114
),
109115
);

e_shoppe/lib/features/order/shipping_info_page.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:e_shoppe/features/order/order_app_bar.dart';
44
import 'package:e_shoppe/features/order/riverpod/order_draft_provider.dart';
5+
import 'package:geocoding/geocoding.dart';
56

67
class ShippingInfoPage extends ConsumerStatefulWidget {
78
const ShippingInfoPage({super.key});
@@ -126,6 +127,29 @@ class _ShippingInfoPageState extends ConsumerState<ShippingInfoPage> {
126127
label: 'Địa chỉ',
127128
controller: _addressCtrl,
128129
onChanged: (v) => notifier.setAddress(v),
130+
onEditingComplete: () async {
131+
final addr = _addressCtrl.text.trim();
132+
if (addr.isEmpty) return;
133+
try {
134+
final locs = await locationFromAddress(addr);
135+
if (locs.isNotEmpty) {
136+
final placemarks = await placemarkFromCoordinates(
137+
locs.first.latitude, locs.first.longitude);
138+
if (placemarks.isNotEmpty) {
139+
final pm = placemarks.first;
140+
if (pm.country != null && pm.country!.isNotEmpty) {
141+
notifier.setCountry(pm.country!);
142+
}
143+
if (pm.administrativeArea != null &&
144+
pm.administrativeArea!.isNotEmpty) {
145+
notifier.setCity(pm.administrativeArea!);
146+
}
147+
}
148+
}
149+
} catch (_) {
150+
// silently ignore geocode failures
151+
}
152+
},
129153
),
130154
_TextRow(
131155
label: 'Ghi chú',
@@ -297,12 +321,14 @@ class _TextRow extends StatelessWidget {
297321
final ValueChanged<String>? onChanged;
298322
final TextInputType? keyboardType;
299323
final int? maxLines;
324+
final VoidCallback? onEditingComplete;
300325
const _TextRow({
301326
required this.label,
302327
required this.controller,
303328
this.onChanged,
304329
this.keyboardType,
305330
this.maxLines,
331+
this.onEditingComplete,
306332
});
307333
@override
308334
Widget build(BuildContext context) {
@@ -321,6 +347,7 @@ class _TextRow extends StatelessWidget {
321347
style: const TextStyle(fontSize: 12),
322348
onChanged: onChanged,
323349
maxLines: maxLines,
350+
onEditingComplete: onEditingComplete,
324351
),
325352
);
326353
}

e_shoppe/lib/services/api_client.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ class ApiClient {
4848

4949
dynamic _decode(http.Response r) {
5050
if (r.body.isEmpty) return null;
51-
return jsonDecode(r.body);
51+
try {
52+
return jsonDecode(r.body);
53+
} catch (_) {
54+
// Not JSON – return raw string.
55+
return r.body;
56+
}
5257
}
5358
}
5459

e_shoppe/pubspec.lock

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,38 @@ packages:
464464
description: flutter
465465
source: sdk
466466
version: "0.0.0"
467+
geocoding:
468+
dependency: "direct main"
469+
description:
470+
name: geocoding
471+
sha256: "790eea732b22a08dd36fc3761bcd29040461ac20ece4d165264a6c0b5338f115"
472+
url: "https://pub.dev"
473+
source: hosted
474+
version: "2.2.2"
475+
geocoding_android:
476+
dependency: transitive
477+
description:
478+
name: geocoding_android
479+
sha256: "1b13eca79b11c497c434678fed109c2be020b158cec7512c848c102bc7232603"
480+
url: "https://pub.dev"
481+
source: hosted
482+
version: "3.3.1"
483+
geocoding_ios:
484+
dependency: transitive
485+
description:
486+
name: geocoding_ios
487+
sha256: "8a39bfb650af55209c42e564036a550b32d029e0733af01dc66c5afea50388d3"
488+
url: "https://pub.dev"
489+
source: hosted
490+
version: "2.3.0"
491+
geocoding_platform_interface:
492+
dependency: transitive
493+
description:
494+
name: geocoding_platform_interface
495+
sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b"
496+
url: "https://pub.dev"
497+
source: hosted
498+
version: "3.2.0"
467499
html:
468500
dependency: transitive
469501
description:

e_shoppe/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dependencies:
5050
firebase_messaging: ^14.7.17
5151
firebase_storage: ^11.6.7
5252
intl: ^0.19.0
53+
geocoding: ^2.1.0
5354

5455
# Media picking
5556
image_picker: ^1.0.5

order_server/bin/seed.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'dart:math';
2+
3+
import 'package:logging/logging.dart';
4+
import 'package:order_server/infrastructure/postgres_repositories.dart';
5+
import 'package:order_server/services/logger.dart';
6+
import 'package:postgres/postgres.dart';
7+
8+
/// Seeds the Postgres database with at least [count] products.
9+
Future<void> main(List<String> args) async {
10+
final count = args.isNotEmpty ? int.parse(args.first) : 100;
11+
12+
initLogging(level: Level.INFO);
13+
final store = await PostgresStore.connectFromEnv();
14+
final conn = store.conn; // expose via getter we add.
15+
16+
await _ensureProducts(conn);
17+
final existing =
18+
(await conn.query('SELECT COUNT(*) FROM products')).first[0] as int;
19+
if (existing >= count) {
20+
log.info('Database already has $existing products – no seeding needed.');
21+
await conn.close();
22+
return;
23+
}
24+
25+
final rand = Random();
26+
final batch =
27+
StringBuffer('INSERT INTO products(name, description, price) VALUES ');
28+
final values = <String>[];
29+
for (int i = existing + 1; i <= count; i++) {
30+
final name = 'Test Product $i';
31+
final desc = 'Auto-generated product #$i';
32+
final price = (rand.nextInt(900) + 100) * 1000; // 100k – 1m
33+
values.add(
34+
"('${name.replaceAll("'", "''")}', '${desc.replaceAll("'", "''")}', $price)");
35+
}
36+
batch.write(values.join(','));
37+
await conn.execute(batch.toString());
38+
log.info('Seeded ${count - existing} products (total $count).');
39+
await conn.close();
40+
}
41+
42+
Future<void> _ensureProducts(PostgreSQLConnection conn) async {
43+
await conn.execute('''
44+
CREATE TABLE IF NOT EXISTS products (
45+
id SERIAL PRIMARY KEY,
46+
name TEXT NOT NULL,
47+
description TEXT NOT NULL,
48+
price DOUBLE PRECISION NOT NULL,
49+
image TEXT
50+
);
51+
''');
52+
}

0 commit comments

Comments
 (0)