Skip to content

Commit 48cbfdc

Browse files
committed
add cart page
1 parent 9dd0155 commit 48cbfdc

File tree

9 files changed

+381
-52
lines changed

9 files changed

+381
-52
lines changed

lib/config/theme/styles.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55
abstract class Styles {
66
//colors
77
static const Color whiteColor = Color(0xffffffff);
8+
static const Color whiteBackground = Color(0xfffdfdfd);
89
static const Color blackColor = Color(0xff000000);
910
static const Color blackThemeColor = Color(0xff17191d);
1011
static const Color whiteThemeColor = Color(0xffffffff);

lib/core/wigets/product_image.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_ecommerce_furniture/config/theme/styles.dart';
3+
import 'package:flutter_ecommerce_furniture/core/ext/buildcontext_ext.dart';
4+
5+
class ProductImage extends StatelessWidget {
6+
final double size;
7+
final String path;
8+
9+
const ProductImage({super.key, this.size = 100, required this.path});
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
final isDark = context.isDarkModeEnabled;
14+
return Container(
15+
width: size,
16+
height: size,
17+
decoration: BoxDecoration(
18+
color: isDark ? Styles.itemColorBgDark : Styles.itemColorBgLight,
19+
borderRadius: BorderRadius.circular(20),
20+
),
21+
child: ClipRRect(
22+
borderRadius: BorderRadius.circular(20),
23+
child: Image.asset(
24+
path,
25+
),
26+
),
27+
);
28+
}
29+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_ecommerce_furniture/core/ext/buildcontext_ext.dart';
3+
import 'package:flutter_ecommerce_furniture/features/product_details/data/mock_data_repo.dart';
4+
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
5+
6+
import '../../../config/theme/styles.dart';
7+
import '../../../core/utils/number_utils.dart';
8+
import '../../../core/wigets/circle_background_widget.dart';
9+
import '../../../core/wigets/product_image.dart';
10+
import '../../product_details/presentation/widgets/checkout_group_button.dart';
11+
import '../../product_details/presentation/widgets/number_selection.dart';
12+
import '../../product_list/data/mock_product_list.dart';
13+
14+
const _rowSize = 120.0;
15+
const _padding = 12.0;
16+
17+
class CartPage extends StatefulWidget {
18+
const CartPage({super.key});
19+
20+
@override
21+
State<CartPage> createState() => _CartPageState();
22+
}
23+
24+
class _CartPageState extends State<CartPage> {
25+
final _listColors = listProductColorsMap.entries.toList();
26+
27+
List<Map<String, dynamic>> _cartProductList =
28+
recommendedProductList.sublist(0, 6);
29+
30+
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
31+
32+
double _totalPrice = 0.0;
33+
34+
void removeItem(int index) {
35+
_listKey.currentState!.removeItem(
36+
index,
37+
(context, animation) => _buildCartItem(index, animation),
38+
);
39+
40+
_cartProductList.removeAt(index);
41+
}
42+
43+
void refreshList() {
44+
setState(() {
45+
_cartProductList = recommendedProductList.sublist(0, 6);
46+
});
47+
}
48+
49+
@override
50+
Widget build(BuildContext context) {
51+
return Container(
52+
padding: const EdgeInsets.symmetric(horizontal: 8),
53+
color: Colors.transparent,
54+
child: Column(
55+
crossAxisAlignment: CrossAxisAlignment.start,
56+
children: [
57+
const SizedBox(
58+
height: 56,
59+
),
60+
const Row(
61+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
62+
children: [
63+
Text(
64+
'My Cart',
65+
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
66+
),
67+
FaIcon(FontAwesomeIcons.search)
68+
],
69+
),
70+
const SizedBox(height: 8),
71+
Expanded(
72+
child: AnimatedList(
73+
key: _listKey,
74+
initialItemCount: _cartProductList.length,
75+
itemBuilder: (BuildContext context, int index, animation) =>
76+
_buildCartItem(index, animation),
77+
),
78+
),
79+
CheckoutGroupButton(
80+
price: _totalPrice,
81+
onPressed: () {
82+
//TODO goto checkout cart
83+
},
84+
actionText: 'Check out',
85+
)
86+
],
87+
),
88+
);
89+
}
90+
91+
Widget _buildCartItem(int index, Animation<double> animation) {
92+
if (index > _cartProductList.length - 1) return Container();
93+
Map<String, dynamic> item = _cartProductList[index];
94+
return FadeTransition(
95+
opacity: animation,
96+
child: SizeTransition(
97+
sizeFactor: animation,
98+
child: CartRowProductItem(
99+
product: item,
100+
selectedColor: _listColors[index].value,
101+
selectedColorName: _listColors[index].key,
102+
onRemoved: (product) {
103+
removeItem(_cartProductList.indexOf(product));
104+
},
105+
onItemPriceChanged: (double itemPrice) {
106+
_calculateTotalPrice(itemPrice);
107+
},
108+
),
109+
),
110+
);
111+
}
112+
113+
_calculateTotalPrice(double itemPrice) {
114+
setState(() {
115+
_totalPrice = itemPrice;
116+
});
117+
}
118+
}
119+
120+
class CartRowProductItem extends StatefulWidget {
121+
final Map<String, dynamic> product;
122+
final Color selectedColor;
123+
final String selectedColorName;
124+
final ValueChanged<Map<String, dynamic>> onRemoved;
125+
final ValueChanged<double> onItemPriceChanged;
126+
127+
const CartRowProductItem({
128+
super.key,
129+
required this.product,
130+
required this.selectedColor,
131+
required this.selectedColorName,
132+
required this.onRemoved,
133+
required this.onItemPriceChanged,
134+
});
135+
136+
@override
137+
State<CartRowProductItem> createState() => _CartRowProductItemState();
138+
}
139+
140+
class _CartRowProductItemState extends State<CartRowProductItem> {
141+
int _numberItem = 1;
142+
double _itemPrice = 0.0;
143+
144+
@override
145+
void initState() {
146+
_itemPrice = widget.product['price'] * _numberItem;
147+
super.initState();
148+
}
149+
150+
@override
151+
Widget build(BuildContext context) {
152+
final isDark = context.isDarkModeEnabled;
153+
return Padding(
154+
padding: const EdgeInsets.all(8.0),
155+
child: Stack(
156+
children: [
157+
Container(
158+
height: _rowSize,
159+
decoration: BoxDecoration(
160+
color: isDark
161+
? Styles.textFieldBackgroundDark
162+
: Styles.textFieldBackgroundLight,
163+
borderRadius: BorderRadius.circular(20),
164+
)),
165+
Padding(
166+
padding: const EdgeInsets.all(8),
167+
child: Row(
168+
crossAxisAlignment: CrossAxisAlignment.center,
169+
mainAxisSize: MainAxisSize.max,
170+
children: [
171+
ProductImage(
172+
size: _rowSize - _padding * 2,
173+
path: widget.product['image']),
174+
const SizedBox(width: 16),
175+
_buildRightContent(),
176+
],
177+
),
178+
)
179+
],
180+
),
181+
);
182+
}
183+
184+
Widget _buildRightContent() {
185+
return Expanded(
186+
child: Container(
187+
padding: const EdgeInsets.all(8),
188+
child: Column(
189+
children: [
190+
_buildTitleAndRemoveIcon(),
191+
const SizedBox(height: 8),
192+
_buildColorContent(),
193+
const SizedBox(height: 8),
194+
_buildPriceAndQuantity()
195+
],
196+
),
197+
),
198+
);
199+
}
200+
201+
Widget _buildPriceAndQuantity() {
202+
return Row(
203+
mainAxisSize: MainAxisSize.max,
204+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
205+
children: [
206+
Text(
207+
'\$${currencyFormat.format(_itemPrice)}',
208+
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
209+
),
210+
NumberSelection(
211+
height: 20,
212+
numberSize: 12,
213+
iconSize: 10,
214+
numberInitial: 2,
215+
onChanged: (number) {
216+
setState(() {
217+
_numberItem = number;
218+
_itemPrice = widget.product['price'] * _numberItem;
219+
widget.onItemPriceChanged.call(_itemPrice);
220+
});
221+
},
222+
)
223+
],
224+
);
225+
}
226+
227+
Widget _buildColorContent() {
228+
return Row(
229+
children: [
230+
CircularBackgroundWidget(
231+
size: 24,
232+
backgroundColor: widget.selectedColor,
233+
child: null,
234+
),
235+
const SizedBox(width: 8),
236+
Text(widget.selectedColorName, style: const TextStyle(fontSize: 14))
237+
],
238+
);
239+
}
240+
241+
Widget _buildTitleAndRemoveIcon() {
242+
return Row(
243+
mainAxisSize: MainAxisSize.max,
244+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
245+
children: [
246+
Expanded(
247+
child: Text(
248+
widget.product['title'],
249+
maxLines: 1,
250+
overflow: TextOverflow.ellipsis,
251+
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
252+
),
253+
),
254+
GestureDetector(
255+
onTap: () {
256+
widget.onRemoved.call(widget.product);
257+
},
258+
child: const Padding(
259+
padding: EdgeInsets.symmetric(horizontal: 8),
260+
child: FaIcon(FontAwesomeIcons.trashCan, size: 18),
261+
),
262+
),
263+
],
264+
);
265+
}
266+
}

lib/features/main/presentation/pages/main_page.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_ecommerce_furniture/core/ext/buildcontext_ext.dart';
33
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
44

55
import '../../../../config/theme/styles.dart';
6+
import '../../../cart/presentation/cart_page.dart';
67
import '../../../home/presentation/home_page.dart';
78
import '../../../profile/presentation/profile_page.dart';
89
import 'example_page.dart';
@@ -42,7 +43,7 @@ class _MainPageState extends State<MainPage> {
4243
},
4344
children: const [
4445
HomePage(),
45-
ExamplePage(titlePage: 'Cart', isNested: true),
46+
CartPage(),
4647
ExamplePage(titlePage: 'Orders', isNested: true),
4748
ExamplePage(titlePage: 'Wallet', isNested: true),
4849
ProfilePage(),
Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import 'dart:ui';
2-
31
import 'package:flutter/material.dart';
42

53
String mockDescriptionText =
64
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
75

8-
List<Color> listProductColors = [
9-
Colors.black,
10-
Colors.white,
11-
Colors.grey,
12-
const Color(0xff763838),
13-
const Color(0xffab8d22),
14-
const Color(0xff0e308d),
15-
];
6+
List<Color> listProductColors =
7+
listProductColorsMap.entries.map((e) => e.value).toList();
8+
9+
Map<String, Color> listProductColorsMap = {
10+
'Black': Colors.black,
11+
'White': Colors.white,
12+
'Grey': Colors.grey,
13+
'Garnet': const Color(0xff763838),
14+
'Cyan': const Color(0xffab8d22),
15+
'Blue': const Color(0xff0e308d),
16+
};

lib/features/product_details/presentation/product_details_page.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class _ProductDetailsPageState extends State<ProductDetailsPage> {
9696
onPressed: () {
9797
//TODO goto checkout cart
9898
},
99+
actionText: 'Add to Cart',
99100
)
100101
],
101102
),

lib/features/product_details/presentation/widgets/checkout_group_button.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ import '../../../../core/utils/number_utils.dart';
66
class CheckoutGroupButton extends StatelessWidget {
77
final double price;
88
final VoidCallback onPressed;
9+
final String actionText;
910

1011
const CheckoutGroupButton(
11-
{super.key, required this.price, required this.onPressed});
12+
{super.key,
13+
required this.price,
14+
required this.onPressed,
15+
required this.actionText});
1216

1317
@override
1418
Widget build(BuildContext context) {
@@ -52,7 +56,7 @@ class CheckoutGroupButton extends StatelessWidget {
5256
),
5357
const SizedBox(width: 8),
5458
Text(
55-
'Add to Cart',
59+
actionText,
5660
style: TextStyle(color: textC, fontWeight: FontWeight.bold),
5761
),
5862
],

0 commit comments

Comments
 (0)