Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
29b7f923
Commit
29b7f923
authored
Apr 26, 2025
by
DatHV
Browse files
update voucher detail
parent
6fcbfba8
Changes
41
Hide whitespace changes
Inline
Side-by-side
lib/screen/voucher/models/product_customer_info_model.dart
0 → 100644
View file @
29b7f923
import
'package:json_annotation/json_annotation.dart'
;
import
'my_product_status_type.dart'
;
part
'product_customer_info_model.g.dart'
;
@JsonSerializable
()
class
ProductCustomerInfoModel
{
final
int
id
;
@JsonKey
(
name:
'status'
)
final
int
?
rawStatus
;
ProductCustomerInfoModel
({
required
this
.
id
,
this
.
rawStatus
,
});
MyProductStatusType
get
status
=>
MyProductStatusType
.
fromRaw
(
rawStatus
??
0
);
factory
ProductCustomerInfoModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ProductCustomerInfoModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$ProductCustomerInfoModelToJson
(
this
);
}
lib/screen/voucher/models/product_customer_info_model.g.dart
0 → 100644
View file @
29b7f923
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'product_customer_info_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProductCustomerInfoModel
_$ProductCustomerInfoModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ProductCustomerInfoModel
(
id:
(
json
[
'id'
]
as
num
).
toInt
(),
rawStatus:
(
json
[
'status'
]
as
num
?)?.
toInt
(),
);
Map
<
String
,
dynamic
>
_$ProductCustomerInfoModelToJson
(
ProductCustomerInfoModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'status'
:
instance
.
rawStatus
};
lib/screen/voucher/models/product_item_model.dart
0 → 100644
View file @
29b7f923
import
'package:json_annotation/json_annotation.dart'
;
part
'product_item_model.g.dart'
;
@JsonSerializable
()
class
ProductItemModel
{
@JsonKey
(
name:
'expire_time'
)
final
String
?
expireTime
;
@JsonKey
(
name:
'code_secret'
)
final
String
?
codeSecret
;
final
String
?
password
;
ProductItemModel
({
this
.
expireTime
,
this
.
codeSecret
,
this
.
password
,
});
factory
ProductItemModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ProductItemModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$ProductItemModelToJson
(
this
);
}
lib/screen/voucher/models/product_item_model.g.dart
0 → 100644
View file @
29b7f923
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'product_item_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProductItemModel
_$ProductItemModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
ProductItemModel
(
expireTime:
json
[
'expire_time'
]
as
String
?,
codeSecret:
json
[
'code_secret'
]
as
String
?,
password:
json
[
'password'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$ProductItemModelToJson
(
ProductItemModel
instance
)
=>
<
String
,
dynamic
>{
'expire_time'
:
instance
.
expireTime
,
'code_secret'
:
instance
.
codeSecret
,
'password'
:
instance
.
password
,
};
lib/screen/voucher/models/product_model.dart
View file @
29b7f923
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_brand_model.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_content_model.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_customer_info_model.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_item_model.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_media_item.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_price_model.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_properties_model.dart'
;
import
'../../flash_sale/preview_flash_sale_model.dart'
;
import
'media_type.dart'
;
import
'my_product_status_type.dart'
;
part
'product_model.g.dart'
;
@JsonSerializable
()
class
ProductModel
{
final
int
?
id
;
@JsonKey
(
name:
'quantity_available'
)
final
int
?
quantityAvailable
;
final
ProductContentModel
?
content
;
...
...
@@ -17,19 +22,57 @@ class ProductModel {
@JsonKey
(
name:
'voucher_properties'
)
final
ProductPropertiesModel
?
properties
;
final
List
<
ProductMediaItem
>?
media
;
@JsonKey
(
name:
'preview_campaign_flash_sale'
)
final
PreviewFlashSale
?
previewFlashSale
;
@JsonKey
(
name:
'customer_product_info'
)
final
ProductCustomerInfoModel
?
customerInfoModel
;
@JsonKey
(
name:
'product_item'
)
final
ProductItemModel
?
itemModel
;
@JsonKey
(
name:
'expire_time'
)
final
String
?
expireTime
;
ProductModel
({
this
.
id
,
this
.
quantityAvailable
,
this
.
content
,
this
.
price
,
this
.
brand
,
this
.
properties
,
this
.
media
,
this
.
previewFlashSale
,
this
.
customerInfoModel
,
this
.
itemModel
,
this
.
expireTime
,
});
String
?
get
name
{
if
(
content
==
null
)
return
null
;
return
content
!.
name
;
return
content
?.
name
;
}
String
?
get
expire
{
return
isMyProduct
?
itemModel
?.
expireTime
:
expireTime
;
}
int
?
get
amountToBePaid
{
if
(
previewFlashSale
?.
isFlashSalePrice
==
true
)
{
return
previewFlashSale
?.
price
;
}
return
price
?.
value
;
}
bool
get
isMyProduct
{
return
customerInfoModel
!=
null
;
}
bool
get
inStock
{
return
(
quantityAvailable
??
1
)
!=
0
;
}
bool
get
expired
{
if
(
customerInfoModel
!=
null
)
{
return
customerInfoModel
?.
status
==
MyProductStatusType
.
expired
;
}
return
(
quantityAvailable
??
1
)
!=
0
;
}
ProductMediaItem
?
get
banner
{
...
...
lib/screen/voucher/models/product_model.g.dart
View file @
29b7f923
...
...
@@ -7,6 +7,7 @@ part of 'product_model.dart';
// **************************************************************************
ProductModel
_$ProductModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
ProductModel
(
id:
(
json
[
'id'
]
as
num
?)?.
toInt
(),
quantityAvailable:
(
json
[
'quantity_available'
]
as
num
?)?.
toInt
(),
content:
json
[
'content'
]
==
null
...
...
@@ -32,14 +33,38 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
(
json
[
'media'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
ProductMediaItem
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
previewFlashSale:
json
[
'preview_campaign_flash_sale'
]
==
null
?
null
:
PreviewFlashSale
.
fromJson
(
json
[
'preview_campaign_flash_sale'
]
as
Map
<
String
,
dynamic
>,
),
customerInfoModel:
json
[
'customer_product_info'
]
==
null
?
null
:
ProductCustomerInfoModel
.
fromJson
(
json
[
'customer_product_info'
]
as
Map
<
String
,
dynamic
>,
),
itemModel:
json
[
'product_item'
]
==
null
?
null
:
ProductItemModel
.
fromJson
(
json
[
'product_item'
]
as
Map
<
String
,
dynamic
>,
),
expireTime:
json
[
'expire_time'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$ProductModelToJson
(
ProductModel
instance
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'quantity_available'
:
instance
.
quantityAvailable
,
'content'
:
instance
.
content
,
'price'
:
instance
.
price
,
'brand'
:
instance
.
brand
,
'voucher_properties'
:
instance
.
properties
,
'media'
:
instance
.
media
,
'preview_campaign_flash_sale'
:
instance
.
previewFlashSale
,
'customer_product_info'
:
instance
.
customerInfoModel
,
'product_item'
:
instance
.
itemModel
,
'expire_time'
:
instance
.
expireTime
,
};
lib/screen/voucher/models/product_price_model.dart
View file @
29b7f923
...
...
@@ -19,19 +19,8 @@ class ProductPriceModel {
});
CashType
get
method
=>
CashTypeExt
.
from
(
paymentMethod
);
int
?
get
value
=>
lastPrice
??
salePrice
;
String
?
get
displayPriceType
{
if
(
value
==
null
)
return
null
;
return
value
!.
makeDisplayPrice
(
method
);
}
String
?
get
displayPriceCommon
{
if
(
value
==
null
)
return
null
;
return
"
${value!.toString()}
đ"
;
// Replace with your number formatting
}
factory
ProductPriceModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ProductPriceModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$ProductPriceModelToJson
(
this
);
}
lib/screen/voucher/models/product_properties_model.dart
View file @
29b7f923
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
part
'product_properties_model.g.dart'
;
@JsonSerializable
()
class
ProductPropertiesModel
{
@JsonKey
(
name:
'voucher_type'
)
final
String
?
voucherType
;
@JsonKey
(
name:
'voucher_value'
)
final
double
?
voucherValue
;
ProductPropertiesModel
({
this
.
voucherType
,
this
.
voucherValue
});
...
...
@@ -13,7 +16,7 @@ class ProductPropertiesModel {
if
(
voucherType
==
"VOUCHER_TYPE_DISCOUNT"
)
{
return
"
${voucherValue!.toStringAsFixed(0)}
%"
;
}
return
"
$
voucherValue
đ"
;
return
voucherValue
!.
money
(
CurrencyUnit
.
vnd
)
;
}
factory
ProductPropertiesModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ProductPropertiesModelFromJson
(
json
);
...
...
lib/screen/voucher/models/product_properties_model.g.dart
View file @
29b7f923
...
...
@@ -9,13 +9,13 @@ part of 'product_properties_model.dart';
ProductPropertiesModel
_$ProductPropertiesModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ProductPropertiesModel
(
voucherType:
json
[
'voucher
T
ype'
]
as
String
?,
voucherValue:
(
json
[
'voucher
V
alue'
]
as
num
?)?.
toDouble
(),
voucherType:
json
[
'voucher
_t
ype'
]
as
String
?,
voucherValue:
(
json
[
'voucher
_v
alue'
]
as
num
?)?.
toDouble
(),
);
Map
<
String
,
dynamic
>
_$ProductPropertiesModelToJson
(
ProductPropertiesModel
instance
,
)
=>
<
String
,
dynamic
>{
'voucher
T
ype'
:
instance
.
voucherType
,
'voucher
V
alue'
:
instance
.
voucherValue
,
'voucher
_t
ype'
:
instance
.
voucherType
,
'voucher
_v
alue'
:
instance
.
voucherValue
,
};
lib/screen/voucher/sub_widget/voucher_action_menu.dart
View file @
29b7f923
...
...
@@ -14,10 +14,10 @@ class VoucherActionMenu extends StatelessWidget {
padding:
const
EdgeInsets
.
symmetric
(
vertical:
12
),
child:
Row
(
children:
const
[
_ActionItem
(
icon:
Icons
.
phone_android
,
label:
'Nạp tiền
\n
diện thoại'
),
_ActionItem
(
icon:
Icons
.
credit_card
,
label:
'Đổi mã
\n
thẻ nạp'
),
_ActionItem
(
icon:
Icons
.
wifi
,
label:
'Gói cước
\n
nhà mạng'
),
_ActionItem
(
icon:
Icons
.
card_giftcard
,
label:
'Ưu đãi
\n
Data'
),
_ActionItem
(
icon:
"assets/images/ic_topup.png"
,
label:
'Nạp tiền
\n
diện thoại'
),
_ActionItem
(
icon:
"assets/images/ic_card_code.png"
,
label:
'Đổi mã
\n
thẻ nạp'
),
_ActionItem
(
icon:
"assets/images/ic_sim_service.png"
,
label:
'Gói cước
\n
nhà mạng'
),
_ActionItem
(
icon:
"assets/images/ic_topup_data.png"
,
label:
'Ưu đãi
\n
Data'
),
],
),
);
...
...
@@ -25,7 +25,7 @@ class VoucherActionMenu extends StatelessWidget {
}
class
_ActionItem
extends
StatelessWidget
{
final
IconData
icon
;
final
String
icon
;
final
String
label
;
const
_ActionItem
({
required
this
.
icon
,
required
this
.
label
});
...
...
@@ -40,7 +40,12 @@ class _ActionItem extends StatelessWidget {
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
icon
,
size:
40
,
color:
BaseColor
.
primary400
),
Image
.
asset
(
icon
,
fit:
BoxFit
.
cover
,
width:
40
,
height:
40
,
),
const
SizedBox
(
height:
8
),
Text
(
label
,
...
...
lib/screen/voucher/sub_widget/voucher_item_grid.dart
View file @
29b7f923
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'../../../widgets/custom_price_tag.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../models/product_model.dart'
;
...
...
@@ -39,7 +41,6 @@ class _VoucherGridItem extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
final
hasDiscount
=
product
.
properties
?.
title
!=
null
;
final
priceText
=
product
.
price
?.
displayPriceType
??
''
;
final
brandName
=
product
.
brand
?.
name
??
''
;
final
brandLogo
=
product
.
brand
?.
logo
??
""
;
final
String
?
bgImage
=
product
.
banner
?.
url
;
...
...
@@ -63,22 +64,51 @@ class _VoucherGridItem extends StatelessWidget {
child:
SizedBox
(
width:
itemWidth
,
height:
itemWidth
/
(
16
/
9
),
child:
loadNetworkImage
(
url:
bgImage
,
placeholderAsset:
"assets/images/sample.png"
),
child:
loadNetworkImage
(
url:
bgImage
,
placeholderAsset:
"assets/images/ic_logo.png"
),
),
),
if
(
hasDiscount
)
Positioned
(
top:
8
,
right:
8
,
top:
0
,
right:
0
,
child:
Container
(
height:
30
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
red
,
borderRadius:
BorderRadius
.
circular
(
12
),
borderRadius:
const
BorderRadius
.
only
(
bottomLeft:
Radius
.
circular
(
8
),
topRight:
Radius
.
circular
(
8
),
),
),
child:
Center
(
child:
Text
(
product
.
properties
?.
title
??
""
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
14
,
fontWeight:
FontWeight
.
bold
),
),
),
),
),
if
(!
product
.
inStock
)
Positioned
(
left:
0
,
bottom:
0
,
child:
Container
(
height:
30
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
4
),
decoration:
BoxDecoration
(
color:
Colors
.
black
.
withOpacity
(
0.6
),
borderRadius:
const
BorderRadius
.
only
(
topRight:
Radius
.
circular
(
8
),
),
),
child:
Text
(
product
.
properties
?.
title
??
""
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
12
),
child:
Center
(
child:
const
Text
(
'Tạm hết'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
14
,
fontWeight:
FontWeight
.
w500
),
),
),
),
),
...
...
@@ -91,6 +121,7 @@ class _VoucherGridItem extends StatelessWidget {
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
SizedBox
(
height:
4
),
// Title: auto co giãn nhưng không tràn
Text
(
product
.
content
?.
name
??
''
,
...
...
@@ -111,7 +142,7 @@ class _VoucherGridItem extends StatelessWidget {
width:
20
,
height:
20
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/
sample.png'
,
// ⚠️ SVG dùng wrong tại đây!
placeholderAsset:
'assets/images/
ic_logo.png'
,
),
),
),
...
...
@@ -123,23 +154,7 @@ class _VoucherGridItem extends StatelessWidget {
overflow:
TextOverflow
.
ellipsis
,
),
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
orange
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
12
,
height:
12
),
const
SizedBox
(
width:
4
),
Text
(
priceText
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
12
),
),
],
),
),
PriceTagWidget
(
point:
product
.
amountToBePaid
??
0
,),
],
),
],
...
...
lib/screen/voucher/sub_widget/voucher_item_list.dart
View file @
29b7f923
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'../../../resouce/base_color.dart'
;
import
'../../../widgets/custom_price_tag.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../models/product_model.dart'
;
...
...
@@ -15,7 +20,12 @@ class VoucherItemList extends StatelessWidget {
physics:
const
NeverScrollableScrollPhysics
(),
itemBuilder:
(
context
,
index
)
{
final
product
=
items
[
index
];
return
VoucherListItem
(
product:
product
);
return
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
voucherDetailScreen
,
arguments:
product
.
id
);
},
child:
VoucherListItem
(
product:
product
),
);
},
);
}
...
...
@@ -30,8 +40,6 @@ class VoucherListItem extends StatelessWidget {
final
productName
=
product
.
content
?.
name
??
''
;
final
brandName
=
product
.
brand
?.
name
??
''
;
final
brandLogo
=
product
.
brand
?.
logo
??
''
;
final
priceText
=
product
.
price
?.
displayPriceType
??
'Miễn phí'
;
final
isFree
=
priceText
.
contains
(
"Miễn phí"
);
final
String
?
bgImage
=
product
.
banner
?.
url
;
return
Column
(
...
...
@@ -42,16 +50,34 @@ class VoucherListItem extends StatelessWidget {
height:
112
,
child:
Row
(
children:
[
// Ảnh banner (16:9)
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
AspectRatio
(
aspectRatio:
16
/
9
,
child:
loadNetworkImage
(
url:
bgImage
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/sample.png'
,
),
child:
Stack
(
children:
[
AspectRatio
(
aspectRatio:
16
/
9
,
child:
loadNetworkImage
(
url:
bgImage
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/sample.png'
,
),
),
if
(!
product
.
inStock
)
Positioned
.
fill
(
child:
Container
(
color:
Colors
.
black
.
withOpacity
(
0.5
),
alignment:
Alignment
.
center
,
child:
const
Text
(
'Tạm hết'
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
],
),
),
const
SizedBox
(
width:
12
),
...
...
@@ -70,7 +96,6 @@ class VoucherListItem extends StatelessWidget {
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
// Logo thương hiệu
CircleAvatar
(
radius:
10
,
backgroundColor:
Colors
.
transparent
,
...
...
@@ -85,7 +110,6 @@ class VoucherListItem extends StatelessWidget {
),
),
const
SizedBox
(
width:
4
),
// Tên thương hiệu
Expanded
(
child:
Text
(
brandName
,
...
...
@@ -96,28 +120,7 @@ class VoucherListItem extends StatelessWidget {
],
),
const
SizedBox
(
height:
8
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
isFree
?
Colors
.
orange
.
shade50
:
Colors
.
red
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
20
,
height:
20
),
const
SizedBox
(
width:
4
),
Text
(
priceText
,
style:
TextStyle
(
fontSize:
12
,
color:
isFree
?
Colors
.
orange
:
Colors
.
red
,
fontWeight:
FontWeight
.
w500
,
),
),
],
),
),
PriceTagWidget
(
point:
product
.
amountToBePaid
??
0
,),
],
),
),
...
...
@@ -127,11 +130,7 @@ class VoucherListItem extends StatelessWidget {
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Divider
(
height:
1
,
thickness:
1
,
color:
BaseColor
.
second200
,
),
child:
Divider
(
height:
1
,
thickness:
1
,
color:
BaseColor
.
second200
),
),
],
);
...
...
lib/screen/voucher/sub_widget/voucher_section_title.dart
View file @
29b7f923
...
...
@@ -24,9 +24,9 @@ class VoucherSectionTitle extends StatelessWidget {
if
(
onViewAll
!=
null
)
GestureDetector
(
onTap:
onViewAll
,
child:
const
Text
(
child:
Text
(
'Xem tất cả'
,
style:
TextStyle
(
color:
Colors
.
blue
),
style:
TextStyle
(
color:
Colors
.
blue
[
700
],
fontWeight:
FontWeight
.
bold
),
),
),
],
...
...
lib/screen/voucher/voucher_list/voucher_list_screen.dart
View file @
29b7f923
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_navigation_bar.dart'
;
import
'../../../widgets/custom_search_navigation_bar.dart'
;
import
'../sub_widget/voucher_item_list.dart'
;
...
...
@@ -37,25 +38,52 @@ class _VoucherListScreenState extends State<VoucherListScreen> {
:
CustomNavigationBar
(
title:
title
),
body:
Column
(
children:
[
if
(
enableSearch
)
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
child:
Obx
(()
{
final
resultCount
=
_viewModel
.
totalResult
.
value
;
final
displayText
=
_viewModel
.
searchQuery
.
isNotEmpty
?
'
$title
(
$resultCount
kết quả)'
:
title
;
return
Align
(
alignment:
Alignment
.
centerLeft
,
child:
Text
(
displayText
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w600
,
),
),
);
}),
),
Expanded
(
child:
Obx
(
()
=>
RefreshIndicator
(
onRefresh:
()
=>
_viewModel
.
getProducts
(
reset:
true
),
child:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
_viewModel
.
products
.
length
+
(
_viewModel
.
hasMore
?
1
:
0
),
itemBuilder:
(
context
,
index
)
{
if
(
index
>=
_viewModel
.
products
.
length
)
{
_viewModel
.
getProducts
(
reset:
false
);
return
const
Center
(
child:
Padding
(
padding:
EdgeInsets
.
all
(
16
),
child:
CircularProgressIndicator
()),
);
}
final
product
=
_viewModel
.
products
[
index
];
return
VoucherListItem
(
product:
product
);
},
),
),
()
{
if
(
_viewModel
.
products
.
isEmpty
)
{
return
const
Center
(
child:
EmptyWidget
(),
);
}
return
RefreshIndicator
(
onRefresh:
()
=>
_viewModel
.
getProducts
(
reset:
true
),
child:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
_viewModel
.
products
.
length
+
(
_viewModel
.
hasMore
?
1
:
0
),
itemBuilder:
(
context
,
index
)
{
if
(
index
>=
_viewModel
.
products
.
length
)
{
_viewModel
.
getProducts
(
reset:
false
);
return
const
Center
(
child:
Padding
(
padding:
EdgeInsets
.
all
(
16
),
child:
CircularProgressIndicator
()),
);
}
final
product
=
_viewModel
.
products
[
index
];
return
VoucherListItem
(
product:
product
);
},
),
);
}
),
),
],
...
...
lib/screen/voucher/voucher_list/voucher_list_viewmodel.dart
View file @
29b7f923
...
...
@@ -18,6 +18,7 @@ class VoucherListViewModel extends RestfulApiViewModel {
bool
_hasMore
=
true
;
bool
get
hasMore
=>
_hasMore
;
String
_searchQuery
=
''
;
String
get
searchQuery
=>
_searchQuery
;
var
totalResult
=
0
.
obs
;
@override
...
...
lib/shared/router_gage.dart
View file @
29b7f923
...
...
@@ -4,6 +4,7 @@ import '../screen/main_tab_screen/main_tab_screen.dart';
import
'../screen/onboarding/onboarding_screen.dart'
;
import
'../screen/setting/setting_screen.dart'
;
import
'../screen/splash/splash_screen.dart'
;
import
'../screen/voucher/detail/voucher_detail_screen.dart'
;
import
'../screen/voucher/voucher_list/voucher_list_screen.dart'
;
const
splashScreen
=
'/splash'
;
...
...
@@ -12,6 +13,7 @@ const loginScreen = '/login';
const
mainScreen
=
'/main'
;
const
settingScreen
=
'/setting'
;
const
vouchersScreen
=
'/vouchers'
;
const
voucherDetailScreen
=
'/voucherDetail'
;
class
RouterPage
{
static
List
<
GetPage
>
pages
()
{
...
...
@@ -28,6 +30,7 @@ class RouterPage {
GetPage
(
name:
mainScreen
,
page:
()
=>
const
MainTabScreen
()),
GetPage
(
name:
settingScreen
,
page:
()
=>
const
SettingScreen
()),
GetPage
(
name:
vouchersScreen
,
page:
()
=>
VoucherListScreen
(),),
GetPage
(
name:
voucherDetailScreen
,
page:
()
=>
VoucherDetailScreen
(),),
];
}
}
lib/widgets/back_button.dart
View file @
29b7f923
...
...
@@ -16,14 +16,14 @@ class CustomBackButton extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
return
SizedBox
(
height:
4
8
,
width:
4
8
,
height:
4
2
,
width:
4
2
,
child:
Stack
(
children:
[
Center
(
child:
Container
(
height:
3
2
,
width:
3
2
,
height:
2
8
,
width:
2
8
,
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
BaseColor
.
second300
,
width:
1
),
borderRadius:
BorderRadius
.
circular
(
8
),
...
...
lib/widgets/custom_empty_widget.dart
0 → 100644
View file @
29b7f923
import
'package:flutter/material.dart'
;
class
EmptyWidget
extends
StatelessWidget
{
final
String
imageAsset
;
final
String
content
;
const
EmptyWidget
({
super
.
key
,
this
.
imageAsset
=
'assets/images/ic_pipi_06.png'
,
this
.
content
=
'Không có dữ liệu hiển thị'
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
Center
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Image
.
asset
(
imageAsset
,
width:
120
,
height:
120
,
fit:
BoxFit
.
contain
,
),
const
SizedBox
(
height:
16
),
Text
(
content
,
textAlign:
TextAlign
.
center
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
grey
,
),
),
],
),
);
}
}
lib/widgets/custom_price_tag.dart
0 → 100644
View file @
29b7f923
import
'package:flutter/material.dart'
;
import
'../extensions/num_extension.dart'
;
import
'../resouce/base_color.dart'
;
class
PriceTagWidget
extends
StatelessWidget
{
final
int
point
;
const
PriceTagWidget
({
super
.
key
,
required
this
.
point
});
bool
get
isFree
=>
point
==
0
;
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
height:
26
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
BaseColor
.
primary150
,
borderRadius:
BorderRadius
.
circular
(
13
),
),
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
16
,
height:
14
),
const
SizedBox
(
width:
4
),
Text
(
isFree
?
'Miễn phí'
:
point
.
money
(
CurrencyUnit
.
none
),
style:
TextStyle
(
fontSize:
12
,
color:
Colors
.
black
,
fontWeight:
FontWeight
.
w500
,
),
),
],
),
);
}
}
lib/widgets/image_loader.dart
View file @
29b7f923
...
...
@@ -5,7 +5,7 @@ Widget loadNetworkImage({
BoxFit
fit
=
BoxFit
.
cover
,
double
?
width
,
double
?
height
,
String
placeholderAsset
=
'assets/images/
sample
.png'
,
String
placeholderAsset
=
'assets/images/
ic_logo
.png'
,
})
{
if
(
url
==
null
||
url
.
isEmpty
)
{
return
Image
.
asset
(
...
...
Prev
1
2
3
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment