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
fa01087d
Commit
fa01087d
authored
Jul 04, 2025
by
DatHV
Browse files
update brand, detail..
parent
c8abf95b
Changes
108
Expand all
Hide whitespace changes
Inline
Side-by-side
lib/screen/mobile_card/models/product_mobile_card_model.g.dart
0 → 100644
View file @
fa01087d
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'product_mobile_card_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProductMobileCardModel
_$ProductMobileCardModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ProductMobileCardModel
(
id:
json
[
'id'
]
as
String
?,
productModelCode:
json
[
'product_model_code'
]
as
String
?,
code:
json
[
'code'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
dataDurationApply:
json
[
'data_duration_apply'
]
as
String
?,
productDescription:
json
[
'description'
]
as
String
?,
startTime:
json
[
'start_time'
]
as
String
?,
endTime:
json
[
'end_time'
]
as
String
?,
limitQuantityPerTransaction:
json
[
'limit_quantity_per_transaction'
]
as
String
?,
brandId:
json
[
'brand_id'
]
as
String
?,
brandCode:
json
[
'brand_code'
]
as
String
?,
brandName:
json
[
'brand_name'
]
as
String
?,
pointReward:
json
[
'point_reward'
]
as
String
?,
brandLogo:
json
[
'brand_logo'
]
as
String
?,
images:
(
json
[
'images'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
MobileCardImageModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
language:
json
[
'language'
]
==
null
?
null
:
MobileCardLanguageModel
.
fromJson
(
json
[
'language'
]
as
Map
<
String
,
dynamic
>,
),
prices:
(
json
[
'prices'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
MobileCardPriceModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
);
Map
<
String
,
dynamic
>
_$ProductMobileCardModelToJson
(
ProductMobileCardModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'product_model_code'
:
instance
.
productModelCode
,
'code'
:
instance
.
code
,
'name'
:
instance
.
name
,
'data_duration_apply'
:
instance
.
dataDurationApply
,
'description'
:
instance
.
productDescription
,
'start_time'
:
instance
.
startTime
,
'end_time'
:
instance
.
endTime
,
'limit_quantity_per_transaction'
:
instance
.
limitQuantityPerTransaction
,
'brand_id'
:
instance
.
brandId
,
'brand_code'
:
instance
.
brandCode
,
'brand_name'
:
instance
.
brandName
,
'point_reward'
:
instance
.
pointReward
,
'brand_logo'
:
instance
.
brandLogo
,
'images'
:
instance
.
images
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
'language'
:
instance
.
language
?.
toJson
(),
'prices'
:
instance
.
prices
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
};
ProductMobileCardResponse
_$ProductMobileCardResponseFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ProductMobileCardResponse
(
products:
(
json
[
'products'
]
as
List
<
dynamic
>?)
?.
map
(
(
e
)
=>
ProductMobileCardModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>),
)
.
toList
(),
);
Map
<
String
,
dynamic
>
_$ProductMobileCardResponseToJson
(
ProductMobileCardResponse
instance
,
)
=>
<
String
,
dynamic
>{
'products'
:
instance
.
products
};
MobileCardImageModel
_$MobileCardImageModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
MobileCardImageModel
(
id:
json
[
'id'
]
as
String
?,
caption:
json
[
'caption'
]
as
String
?,
imageUrl:
json
[
'image_url'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$MobileCardImageModelToJson
(
MobileCardImageModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'caption'
:
instance
.
caption
,
'image_url'
:
instance
.
imageUrl
,
};
MobileCardLanguageModel
_$MobileCardLanguageModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
MobileCardLanguageModel
(
content:
json
[
'content'
]
as
String
?,
termAndCondition:
json
[
'term_and_condition'
]
as
String
?,
stockRemark:
json
[
'stock_remark'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$MobileCardLanguageModelToJson
(
MobileCardLanguageModel
instance
,
)
=>
<
String
,
dynamic
>{
'content'
:
instance
.
content
,
'term_and_condition'
:
instance
.
termAndCondition
,
'stock_remark'
:
instance
.
stockRemark
,
};
MobileCardPriceModel
_$MobileCardPriceModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
MobileCardPriceModel
(
channelCode:
json
[
'channel_code'
]
as
String
?,
channelName:
json
[
'channel_name'
]
as
String
?,
payPoint:
json
[
'pay_point'
]
as
String
?,
originalPrice:
json
[
'original_price'
]
as
String
?,
poolCode:
json
[
'pool_code'
]
as
String
?,
subPoolCode:
json
[
'sub_pool_code'
]
as
String
?,
currencyCode:
json
[
'currency_code'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$MobileCardPriceModelToJson
(
MobileCardPriceModel
instance
,
)
=>
<
String
,
dynamic
>{
'channel_code'
:
instance
.
channelCode
,
'channel_name'
:
instance
.
channelName
,
'pay_point'
:
instance
.
payPoint
,
'original_price'
:
instance
.
originalPrice
,
'pool_code'
:
instance
.
poolCode
,
'sub_pool_code'
:
instance
.
subPoolCode
,
'currency_code'
:
instance
.
currencyCode
,
};
lib/screen/mobile_card/models/usable_voucher_model.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/foundation.dart'
;
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/screen/mobile_card/models/product_mobile_card_model.dart'
;
import
'../../home/models/brand_model.dart'
;
part
'usable_voucher_model.g.dart'
;
@JsonSerializable
()
class
UsableVoucherModel
{
@JsonKey
(
name:
'voucher_id'
)
final
String
?
voucherID
;
@JsonKey
(
name:
'product_item_id'
)
final
String
?
voucherItemID
;
@JsonKey
(
name:
'action_time'
)
final
String
?
actionTime
;
final
String
?
code
;
final
String
?
serial
;
String
?
name
;
String
?
description
;
@JsonKey
(
name:
'voucher_type_code'
)
final
String
?
voucherTypeCode
;
@JsonKey
(
name:
'voucher_type_name'
)
final
String
?
voucherTypeName
;
@JsonKey
(
name:
'voucher_value'
)
final
String
?
voucherValue
;
@JsonKey
(
name:
'content'
)
final
String
?
voucherContent
;
@JsonKey
(
name:
'term_and_condition'
)
final
String
?
voucherTermAndCondition
;
@JsonKey
(
name:
'voucher_stock_remark'
)
final
String
?
voucherStockRemark
;
@JsonKey
(
name:
'expired_time'
)
final
String
?
expiredTime
;
@JsonKey
(
name:
'status_code'
)
final
String
?
statusCode
;
final
String
?
status
;
@JsonKey
(
name:
'beneficiary_site_name'
)
final
String
?
beneficiarySiteName
;
final
List
<
MobileCardPriceModel
>?
prices
;
final
List
<
MobileCardImageModel
>?
images
;
final
BrandModel
?
brand
;
@JsonKey
(
name:
'like_id'
)
final
String
?
likeId
;
@JsonKey
(
name:
'code_secret'
)
String
?
codeSecret
;
final
String
?
password
;
UsableVoucherModel
({
this
.
voucherID
,
this
.
voucherItemID
,
this
.
actionTime
,
this
.
code
,
this
.
serial
,
this
.
name
,
this
.
description
,
this
.
voucherTypeCode
,
this
.
voucherTypeName
,
this
.
voucherValue
,
this
.
voucherContent
,
this
.
voucherTermAndCondition
,
this
.
voucherStockRemark
,
this
.
expiredTime
,
this
.
statusCode
,
this
.
status
,
this
.
beneficiarySiteName
,
this
.
prices
,
this
.
images
,
this
.
brand
,
this
.
likeId
,
this
.
codeSecret
,
this
.
password
,
});
factory
UsableVoucherModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$UsableVoucherModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$UsableVoucherModelToJson
(
this
);
}
@JsonSerializable
()
class
RedeemProductResponseModel
{
final
UsableVoucherModel
?
item
;
RedeemProductResponseModel
({
this
.
item
});
factory
RedeemProductResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$RedeemProductResponseModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$RedeemProductResponseModelToJson
(
this
);
}
lib/screen/mobile_card/models/usable_voucher_model.g.dart
0 → 100644
View file @
fa01087d
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'usable_voucher_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UsableVoucherModel
_$UsableVoucherModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
UsableVoucherModel
(
voucherID:
json
[
'voucher_id'
]
as
String
?,
voucherItemID:
json
[
'product_item_id'
]
as
String
?,
actionTime:
json
[
'action_time'
]
as
String
?,
code:
json
[
'code'
]
as
String
?,
serial:
json
[
'serial'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
description:
json
[
'description'
]
as
String
?,
voucherTypeCode:
json
[
'voucher_type_code'
]
as
String
?,
voucherTypeName:
json
[
'voucher_type_name'
]
as
String
?,
voucherValue:
json
[
'voucher_value'
]
as
String
?,
voucherContent:
json
[
'content'
]
as
String
?,
voucherTermAndCondition:
json
[
'term_and_condition'
]
as
String
?,
voucherStockRemark:
json
[
'voucher_stock_remark'
]
as
String
?,
expiredTime:
json
[
'expired_time'
]
as
String
?,
statusCode:
json
[
'status_code'
]
as
String
?,
status:
json
[
'status'
]
as
String
?,
beneficiarySiteName:
json
[
'beneficiary_site_name'
]
as
String
?,
prices:
(
json
[
'prices'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
MobileCardPriceModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
images:
(
json
[
'images'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
MobileCardImageModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
brand:
json
[
'brand'
]
==
null
?
null
:
BrandModel
.
fromJson
(
json
[
'brand'
]
as
Map
<
String
,
dynamic
>),
likeId:
json
[
'like_id'
]
as
String
?,
codeSecret:
json
[
'code_secret'
]
as
String
?,
password:
json
[
'password'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$UsableVoucherModelToJson
(
UsableVoucherModel
instance
)
=>
<
String
,
dynamic
>{
'voucher_id'
:
instance
.
voucherID
,
'product_item_id'
:
instance
.
voucherItemID
,
'action_time'
:
instance
.
actionTime
,
'code'
:
instance
.
code
,
'serial'
:
instance
.
serial
,
'name'
:
instance
.
name
,
'description'
:
instance
.
description
,
'voucher_type_code'
:
instance
.
voucherTypeCode
,
'voucher_type_name'
:
instance
.
voucherTypeName
,
'voucher_value'
:
instance
.
voucherValue
,
'content'
:
instance
.
voucherContent
,
'term_and_condition'
:
instance
.
voucherTermAndCondition
,
'voucher_stock_remark'
:
instance
.
voucherStockRemark
,
'expired_time'
:
instance
.
expiredTime
,
'status_code'
:
instance
.
statusCode
,
'status'
:
instance
.
status
,
'beneficiary_site_name'
:
instance
.
beneficiarySiteName
,
'prices'
:
instance
.
prices
,
'images'
:
instance
.
images
,
'brand'
:
instance
.
brand
,
'like_id'
:
instance
.
likeId
,
'code_secret'
:
instance
.
codeSecret
,
'password'
:
instance
.
password
,
};
RedeemProductResponseModel
_$RedeemProductResponseModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
RedeemProductResponseModel
(
item:
json
[
'item'
]
==
null
?
null
:
UsableVoucherModel
.
fromJson
(
json
[
'item'
]
as
Map
<
String
,
dynamic
>),
);
Map
<
String
,
dynamic
>
_$RedeemProductResponseModelToJson
(
RedeemProductResponseModel
instance
,
)
=>
<
String
,
dynamic
>{
'item'
:
instance
.
item
};
lib/screen/mobile_card/product_mobile_card_screen.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'package:mypoint_flutter_app/screen/mobile_card/product_mobile_card_viewmodel.dart'
;
import
'package:mypoint_flutter_app/screen/mobile_card/usable_mobile_card_popup.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_app_bar.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../widgets/alert/custom_alert_dialog.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
class
ProductMobileCardScreen
extends
BaseScreen
{
const
ProductMobileCardScreen
({
super
.
key
});
@override
State
<
ProductMobileCardScreen
>
createState
()
=>
_ProductMobileCardScreenState
();
}
class
_ProductMobileCardScreenState
extends
BaseState
<
ProductMobileCardScreen
>
with
BasicState
{
late
final
ProductMobileCardViewModel
_viewModel
;
@override
void
initState
()
{
super
.
initState
();
_viewModel
=
ProductMobileCardViewModel
();
_viewModel
.
getProductMobileCard
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
_viewModel
.
onRedeemProductMobileSuccess
=
(
data
)
{
if
(
data
!=
null
)
{
showVoucherPopup
(
context
,
data
);
}
else
{
showAlertError
(
content:
"Đổi mã thẻ nạp thất bại, vui lòng thử lại sau!"
);
}
};
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomAppBar
.
back
(
title:
"Đổi mã thẻ nạp"
),
body:
Obx
(()
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
child:
Text
(
"Chọn nhà mạng"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
)),
),
_buildSectionNetwork
(),
const
SizedBox
(
height:
24
),
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
"Mệnh giá thẻ"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
)),
),
const
SizedBox
(
height:
12
),
_buildProductItem
(),
SafeArea
(
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
ElevatedButton
(
onPressed:
_viewModel
.
selectedProduct
==
null
?
null
:
_redeemProductMobileCard
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
_viewModel
.
selectedProduct
==
null
?
Colors
.
grey
:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
)),
minimumSize:
const
Size
.
fromHeight
(
48
),
),
child:
const
Text
(
"Xác nhận"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
),
),
),
],
);
}),
);
}
Widget
_buildSectionNetwork
()
{
final
widthCardItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
2.5
;
return
SizedBox
(
height:
widthCardItem
*
9
/
16
,
child:
ListView
.
separated
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
scrollDirection:
Axis
.
horizontal
,
itemCount:
_viewModel
.
mobileCardSections
.
value
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
_
,
index
)
{
final
mobileCard
=
_viewModel
.
mobileCardSections
.
value
[
index
];
final
isSelected
=
mobileCard
.
brandCode
==
_viewModel
.
selectedBrandCode
.
value
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
if
(
_viewModel
.
selectedBrandCode
.
value
==
mobileCard
.
brandCode
)
return
;
_viewModel
.
selectedBrandCode
.
value
=
mobileCard
.
brandCode
??
""
;
_viewModel
.
selectedProduct
=
null
;
});
},
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
20
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
12
),
border:
Border
.
all
(
color:
isSelected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
,
width:
2
),
color:
Colors
.
white
,
),
alignment:
Alignment
.
center
,
child:
loadNetworkImage
(
url:
mobileCard
.
brandLogo
,
width:
widthCardItem
,
placeholderAsset:
"assets/images/bg_default_169.png"
,
),
),
);
},
),
);
}
Widget
_buildProductItem
()
{
return
Expanded
(
child:
GridView
.
count
(
crossAxisCount:
2
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
childAspectRatio:
2.4
,
children:
_viewModel
.
products
.
map
((
product
)
{
final
isSelected
=
_viewModel
.
selectedProduct
?.
id
==
product
.
id
;
final
amount
=
int
.
tryParse
(
(
product
.
prices
?.
isNotEmpty
==
true
)
?
product
.
prices
?.
first
.
originalPrice
??
"0"
:
"0"
,
)
??
0
;
final
price
=
int
.
tryParse
((
product
.
prices
?.
isNotEmpty
==
true
)
?
product
.
prices
?.
first
.
payPoint
??
"0"
:
"0"
)
??
0
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
_viewModel
.
selectedProduct
=
product
;
});
},
child:
Container
(
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
isSelected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
12
),
color:
isSelected
?
Colors
.
orange
.
withOpacity
(
0.1
)
:
Colors
.
white
,
),
padding:
const
EdgeInsets
.
all
(
12
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
amount
.
money
(
CurrencyUnit
.
vnd
),
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
,
color:
isSelected
?
Colors
.
orange
:
Colors
.
black87
,
),
),
const
SizedBox
(
height:
4
),
Row
(
children:
[
Text
(
"Giá:
${price.money(CurrencyUnit.none)}
"
,
style:
TextStyle
(
fontSize:
14
,
color:
isSelected
?
Colors
.
orange
:
Colors
.
black54
),
),
Image
.
asset
(
"assets/images/ic_point.png"
,
width:
16
,
height:
16
),
],
),
],
),
),
);
}).
toList
(),
),
);
}
_redeemProductMobileCard
()
{
if
(
_viewModel
.
selectedProduct
==
null
)
return
;
if
(!
_viewModel
.
isValidBalance
)
{
showAlertError
(
content:
"Bạn chưa đủ điểm để đổi ưu đãi này, vui lòng tích lũy thêm điểm nhé!"
);
return
;
}
_showAlertConfirmRedeemProduct
();
}
_showAlertConfirmRedeemProduct
()
{
final
dataAlert
=
DataAlertModel
(
title:
"Xác nhận"
,
description:
"Bạn có muốn sử dụng
${_viewModel.payPoint.money(CurrencyUnit.point)}
MyPoint để đổi lấy mã thẻ điện thoại này không?"
,
localHeaderImage:
"assets/images/ic_pipi_02.png"
,
buttons:
[
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
{
Get
.
back
();
_viewModel
.
redeemProductMobileCard
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Huỷ"
,
onPressed:
()
=>
Get
.
back
(),
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
),
],
);
showAlert
(
data:
dataAlert
,
direction:
ButtonsDirection
.
row
);
}
}
lib/screen/mobile_card/product_mobile_card_viewmodel.dart
0 → 100644
View file @
fa01087d
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/configs/constants.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:mypoint_flutter_app/screen/mobile_card/models/product_mobile_card_model.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../preference/point/point_manager.dart'
;
import
'models/usable_voucher_model.dart'
;
class
ProductMobileCardViewModel
extends
RestfulApiViewModel
{
void
Function
(
String
message
)?
onShowAlertError
;
void
Function
(
UsableVoucherModel
data
)?
onRedeemProductMobileSuccess
;
RxMap
<
String
,
List
<
ProductMobileCardModel
>>
groupedSection
=
RxMap
<
String
,
List
<
ProductMobileCardModel
>>();
var
mobileCardSections
=
RxList
<
ProductMobileCardModel
>();
RxString
selectedBrandCode
=
""
.
obs
;
List
<
ProductMobileCardModel
>
get
products
{
return
groupedSection
[
selectedBrandCode
.
value
]
??
[];
}
ProductMobileCardModel
?
selectedProduct
;
int
get
payPoint
{
return
int
.
tryParse
(
selectedProduct
?.
prices
?.
firstOrNull
?.
payPoint
??
"0"
)
??
0
;
}
bool
get
isValidBalance
{
return
UserPointManager
().
point
>=
(
int
.
tryParse
(
selectedProduct
?.
prices
?.
firstOrNull
?.
payPoint
??
"0"
)
??
0
);
}
@override
onInit
()
{
super
.
onInit
();
UserPointManager
().
fetchUserPoint
();
}
redeemProductMobileCard
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
redeemMobileCard
((
selectedProduct
?.
id
??
0
).
toString
());
final
itemId
=
response
.
data
?.
itemId
??
""
;
hideLoading
();
if
(
itemId
.
isEmpty
)
{
hideLoading
();
print
(
"redeemMobileCard failed:
${response.errorMessage}
"
);
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
return
;
}
_getMobileCardCode
(
itemId
);
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
error
.
toString
());
return
;
}
}
_getMobileCardCode
(
String
itemId
)
async
{
showLoading
();
try
{
final
response
=
await
client
.
getMobileCardCode
(
itemId
);
hideLoading
();
final
data
=
response
.
data
?.
item
;
if
(
response
.
isSuccess
&&
data
!=
null
)
{
onRedeemProductMobileSuccess
?.
call
(
data
);
return
;
}
onShowAlertError
?.
call
(
response
.
message
??
Constants
.
commonError
);
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
error
.
toString
());
return
;
}
}
getProductMobileCard
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
productMobileCardGetList
();
final
result
=
response
.
data
?.
products
??
[];
final
seen
=
<
String
>{};
final
uniqueBrandCode
=
<
ProductMobileCardModel
>[];
for
(
final
p
in
result
)
{
final
code
=
p
.
brandCode
??
""
;
if
(
code
.
isNotEmpty
&&
seen
.
add
(
code
))
{
uniqueBrandCode
.
add
(
p
);
}
}
selectedBrandCode
.
value
=
uniqueBrandCode
.
isNotEmpty
?
uniqueBrandCode
.
first
.
brandCode
??
""
:
""
;
mobileCardSections
.
value
=
uniqueBrandCode
;
final
Map
<
String
,
List
<
ProductMobileCardModel
>>
grouped
=
{};
for
(
final
product
in
result
)
{
final
code
=
product
.
brandCode
??
'unknown'
;
if
(!
grouped
.
containsKey
(
code
))
{
grouped
[
code
]
=
[];
}
grouped
[
code
]!.
add
(
product
);
}
groupedSection
.
value
=
grouped
;
hideLoading
();
if
(!
response
.
isSuccess
)
{
onShowAlertError
?.
call
(
response
.
message
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
onShowAlertError
?.
call
(
error
.
toString
());
}
}
}
lib/screen/mobile_card/usable_mobile_card_popup.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'models/usable_voucher_model.dart'
;
class
UsableMobileCardPopup
extends
StatelessWidget
{
final
UsableVoucherModel
usableVoucher
;
const
UsableMobileCardPopup
({
super
.
key
,
required
this
.
usableVoucher
});
@override
Widget
build
(
BuildContext
context
)
{
final
String
titleNonExpired
=
"Bạn đã đổi mã thẻ điện thoại thành công."
;
final
String
titleSuccess
=
"Bạn đã đổi mã thẻ điện thoại thành công. Giá trị tới ngày %@. Vui lòng sử dụng trước ngày hết hạn."
;
final
expiredTime
=
(
usableVoucher
.
expiredTime
??
""
).
toDate
()?.
toFormattedString
()
??
""
;
final
String
content
=
expiredTime
.
isNotEmpty
?
titleSuccess
.
replaceAll
(
"%@"
,
usableVoucher
.
expiredTime
!)
:
titleNonExpired
;
return
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
)),
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Align
(
alignment:
Alignment
.
topRight
,
child:
GestureDetector
(
onTap:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
const
Icon
(
Icons
.
close
,
size:
24
),
),
),
Image
.
asset
(
'assets/images/ic_pipi_02.png'
,
height:
200
,
),
const
SizedBox
(
height:
8
),
Text
(
"Thành Công"
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
height:
8
),
Text
(
content
,
textAlign:
TextAlign
.
center
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
),
),
const
SizedBox
(
height:
16
),
Container
(
height:
48
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
color:
Colors
.
grey
.
shade200
,
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
child:
Row
(
children:
[
Expanded
(
child:
Text
(
usableVoucher
.
codeSecret
??
'---'
,
style:
const
TextStyle
(
fontSize:
16
),
),
),
GestureDetector
(
onTap:
()
{
Clipboard
.
setData
(
ClipboardData
(
text:
usableVoucher
.
codeSecret
??
''
),
);
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Đã sao chép mã'
)),
);
},
child:
const
Icon
(
Icons
.
copy
),
),
],
),
),
const
SizedBox
(
height:
20
),
ElevatedButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
style:
ElevatedButton
.
styleFrom
(
minimumSize:
const
Size
(
double
.
infinity
,
48
),
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
"Đã hiểu"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
)),
),
],
),
),
);
}
}
void
showVoucherPopup
(
BuildContext
context
,
UsableVoucherModel
usableVoucher
)
{
showDialog
(
context:
context
,
barrierDismissible:
true
,
builder:
(
_
)
=>
UsableMobileCardPopup
(
usableVoucher:
usableVoucher
),
);
}
lib/screen/personal/personal_edit_viewmodel.dart
View file @
fa01087d
...
@@ -34,9 +34,10 @@ class PersonalEditViewModel extends RestfulApiViewModel {
...
@@ -34,9 +34,10 @@ class PersonalEditViewModel extends RestfulApiViewModel {
);
);
birthday
=
profile
?.
workerSite
?.
birthday
?.
toDateFormat
(
'yyyy-MM-dd'
);
birthday
=
profile
?.
workerSite
?.
birthday
?.
toDateFormat
(
'yyyy-MM-dd'
);
gender
=
PersonalGender
.
from
(
profile
.
workerSite
?.
sex
??
"U"
);
gender
=
PersonalGender
.
from
(
profile
.
workerSite
?.
sex
??
"U"
);
var
name
=
profile
?.
workerSite
?.
fullname
??
""
;
editDataModel
.
value
=
PersonalEditDataModel
(
editDataModel
.
value
=
PersonalEditDataModel
(
name:
DataPreference
.
instance
.
fullN
ame
,
name:
n
ame
,
nickname:
profile
?.
workerSite
?.
nickname
,
nickname:
profile
?.
workerSite
?.
nickname
,
phone:
profile
?.
workerSite
?.
phoneNumber
,
phone:
profile
?.
workerSite
?.
phoneNumber
,
email:
profile
?.
workerSite
?.
email
,
email:
profile
?.
workerSite
?.
email
,
...
...
lib/screen/personal/personal_screen.dart
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/directional/directional_screen.dart'
;
import
'package:mypoint_flutter_app/directional/directional_screen.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/preference/data_preference.dart'
;
import
'package:mypoint_flutter_app/preference/data_preference.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../base/basic_state.dart'
;
...
@@ -64,14 +65,14 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
...
@@ -64,14 +65,14 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
Widget
_buildHeaderPersonal
(
HeaderHomeModel
data
)
{
Widget
_buildHeaderPersonal
(
HeaderHomeModel
data
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
topPadding
=
MediaQuery
.
of
(
context
).
padding
.
top
;
final
topPadding
=
MediaQuery
.
of
(
context
).
padding
.
top
;
final
name
=
DataPreference
.
instance
.
profile
?.
workerSite
?.
fullname
??
"Quý Khách"
;
final
name
=
DataPreference
.
instance
.
displayName
;
final
level
=
DataPreference
.
instance
.
rankName
??
"Hạng Đồng"
;
final
level
=
DataPreference
.
instance
.
rankName
??
"Hạng Đồng"
;
final
email
=
DataPreference
.
instance
.
profile
?.
workerSite
?.
email
??
""
;
final
email
=
DataPreference
.
instance
.
profile
?.
workerSite
?.
email
??
""
;
return
Container
(
return
Container
(
height:
width
*
163
/
375
,
height:
width
*
163
/
375
,
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
NetworkImage
(
data
.
background
??
""
),
fit:
BoxFit
.
cover
)),
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
NetworkImage
(
data
.
background
??
""
),
fit:
BoxFit
.
cover
)),
child:
Padding
(
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
8
),
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
...
@@ -125,7 +126,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
...
@@ -125,7 +126,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
Row
(
Row
(
children:
[
children:
[
Text
(
Text
(
"
${
data.totalPointActive
.toString()}
điểm"
,
(
data
.
totalPointActive
??
0
).
money
(
CurrencyUnit
.
point
)
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
),
),
const
SizedBox
(
width:
4
),
const
SizedBox
(
width:
4
),
...
@@ -182,7 +183,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
...
@@ -182,7 +183,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
'icon'
:
Icons
.
receipt_long_outlined
,
'title'
:
'Lịch sử giao dịch'
,
'sectionDivider'
:
true
,
'type'
:
''
},
{
'icon'
:
Icons
.
receipt_long_outlined
,
'title'
:
'Lịch sử giao dịch'
,
'sectionDivider'
:
true
,
'type'
:
''
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử điểm'
,
'type'
:
''
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử điểm'
,
'type'
:
''
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử hoàn điểm'
,
'type'
:
''
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử hoàn điểm'
,
'type'
:
'
APP_SCREEN_REFUND_HISTORY
'
},
{
'icon'
:
Icons
.
account_balance_wallet_outlined
,
'title'
:
'Quản lý tài khoản/thẻ'
,
'type'
:
''
},
{
'icon'
:
Icons
.
account_balance_wallet_outlined
,
'title'
:
'Quản lý tài khoản/thẻ'
,
'type'
:
''
},
{
'icon'
:
Icons
.
favorite_border
,
'title'
:
'Yêu thích'
,
'type'
:
''
},
{
'icon'
:
Icons
.
favorite_border
,
'title'
:
'Yêu thích'
,
'type'
:
''
},
{
'icon'
:
Icons
.
shopping_bag_outlined
,
'title'
:
'Đơn mua'
,
'sectionDivider'
:
true
,
'type'
:
'APP_SCREEN_ORDER_MENU'
},
{
'icon'
:
Icons
.
shopping_bag_outlined
,
'title'
:
'Đơn mua'
,
'sectionDivider'
:
true
,
'type'
:
'APP_SCREEN_ORDER_MENU'
},
...
...
lib/screen/topup/brand_select_sheet_widget.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../voucher/models/product_brand_model.dart'
;
class
BrandSelectSheet
extends
StatelessWidget
{
final
List
<
ProductBrandModel
>
brands
;
final
Function
(
ProductBrandModel
)
onSelected
;
final
ProductBrandModel
?
selectedBrand
;
const
BrandSelectSheet
({
super
.
key
,
required
this
.
brands
,
this
.
selectedBrand
,
required
this
.
onSelected
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
padding:
const
EdgeInsets
.
fromLTRB
(
16
,
16
,
16
,
32
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
vertical
(
top:
Radius
.
circular
(
24
)),
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
const
Text
(
"Chọn nhà mạng"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
18
)),
const
SizedBox
(
height:
16
),
GridView
.
builder
(
shrinkWrap:
true
,
itemCount:
brands
.
length
,
physics:
const
NeverScrollableScrollPhysics
(),
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
3
,
mainAxisSpacing:
16
,
crossAxisSpacing:
16
,
childAspectRatio:
1.7
,
),
itemBuilder:
(
context
,
index
)
{
final
brand
=
brands
[
index
];
bool
isFocused
=
selectedBrand
?.
id
==
brand
.
id
;
return
GestureDetector
(
onTap:
()
=>
onSelected
(
brand
),
child:
Container
(
padding:
const
EdgeInsets
.
all
(
8
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
isFocused
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
loadNetworkImage
(
url:
brand
.
logo
,
fit:
BoxFit
.
contain
,
placeholderAsset:
"assets/images/bg_default_169.png"
,
),
),
);
},
),
],
),
);
}
}
lib/screen/topup/models/brand_network_model.dart
0 → 100644
View file @
fa01087d
import
'package:json_annotation/json_annotation.dart'
;
part
'brand_network_model.g.dart'
;
@JsonSerializable
()
class
BrandNetworkModel
{
final
int
?
id
;
final
int
?
stock
;
final
String
?
code
;
final
String
?
name
;
final
String
?
logo
;
BrandNetworkModel
({
this
.
id
,
this
.
stock
,
this
.
code
,
this
.
name
,
this
.
logo
,
});
factory
BrandNetworkModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$BrandNetworkModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$BrandNetworkModelToJson
(
this
);
}
@JsonSerializable
()
class
BrandNameCheckResponse
{
final
String
?
brand
;
BrandNameCheckResponse
({
this
.
brand
});
factory
BrandNameCheckResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$BrandNameCheckResponseFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$BrandNameCheckResponseToJson
(
this
);
}
lib/screen/topup/models/brand_network_model.g.dart
0 → 100644
View file @
fa01087d
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'brand_network_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BrandNetworkModel
_$BrandNetworkModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
BrandNetworkModel
(
id:
(
json
[
'id'
]
as
num
?)?.
toInt
(),
stock:
(
json
[
'stock'
]
as
num
?)?.
toInt
(),
code:
json
[
'code'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
logo:
json
[
'logo'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$BrandNetworkModelToJson
(
BrandNetworkModel
instance
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'stock'
:
instance
.
stock
,
'code'
:
instance
.
code
,
'name'
:
instance
.
name
,
'logo'
:
instance
.
logo
,
};
BrandNameCheckResponse
_$BrandNameCheckResponseFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
BrandNameCheckResponse
(
brand:
json
[
'brand'
]
as
String
?);
Map
<
String
,
dynamic
>
_$BrandNameCheckResponseToJson
(
BrandNameCheckResponse
instance
,
)
=>
<
String
,
dynamic
>{
'brand'
:
instance
.
brand
};
lib/screen/topup/topup_screen.dart
0 → 100644
View file @
fa01087d
import
'package:contacts_service/contacts_service.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:intl/intl.dart'
;
import
'package:mypoint_flutter_app/screen/topup/topup_viewmodel.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_navigation_bar.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'brand_select_sheet_widget.dart'
;
class
PhoneTopUpScreen
extends
StatefulWidget
{
const
PhoneTopUpScreen
({
super
.
key
});
@override
State
<
PhoneTopUpScreen
>
createState
()
=>
_PhoneTopUpScreenState
();
}
class
_PhoneTopUpScreenState
extends
State
<
PhoneTopUpScreen
>
{
final
TopUpViewModel
_viewModel
=
Get
.
put
(
TopUpViewModel
());
late
final
TextEditingController
_phoneController
;
@override
void
initState
()
{
super
.
initState
();
_phoneController
=
TextEditingController
(
text:
_viewModel
.
phoneNumber
.
value
);
_viewModel
.
firstLoadTopUpData
();
}
String
get
formattedAmount
{
return
NumberFormat
.
currency
(
locale:
'vi_VN'
,
symbol:
''
,
decimalDigits:
0
,
).
format
(
_viewModel
.
selectedProduct
.
value
?.
amountToBePaid
??
0
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Nạp tiền điện thoại"
),
body:
Obx
(()
{
return
Column
(
children:
[
_buildHeaderPhone
(),
Container
(
height:
6
,
color:
Colors
.
grey
.
shade200
),
const
Divider
(
height:
8
),
_buildItemTypeProduct
(),
const
Divider
(
height:
1
),
],
);
}),
bottomNavigationBar:
Obx
(()
{
return
_buildBottomAction
();
}),
);
}
Widget
_buildHeaderPhone
()
{
return
Obx
(()
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
SizedBox
(
height:
8
),
const
Text
(
"Số điện thoại"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
)),
const
SizedBox
(
height:
8
),
Row
(
children:
[
Expanded
(
child:
TextField
(
controller:
_phoneController
,
decoration:
InputDecoration
(
filled:
true
,
fillColor:
Colors
.
grey
.
shade100
,
suffixIcon:
InkWell
(
onTap:
()
=>
pickContact
(
context
),
child:
const
Icon
(
Icons
.
contacts
,
color:
Colors
.
orange
),
),
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
.
none
),
),
keyboardType:
TextInputType
.
phone
,
onChanged:
(
value
)
{
_viewModel
.
phoneNumber
.
value
=
value
;
_viewModel
.
checkMobileNetwork
();
},
),
),
const
SizedBox
(
width:
8
),
GestureDetector
(
onTap:
_viewModel
.
topUpBrands
.
value
.
isEmpty
?
null
:
()
{
showModalBottomSheet
(
context:
context
,
backgroundColor:
Colors
.
transparent
,
isScrollControlled:
true
,
builder:
(
_
)
=>
BrandSelectSheet
(
brands:
_viewModel
.
topUpBrands
.
value
,
selectedBrand:
_viewModel
.
selectedBrand
.
value
,
onSelected:
(
brand
)
{
Navigator
.
pop
(
context
);
if
(
brand
==
null
&&
brand
.
id
!=
_viewModel
.
selectedBrand
.
value
?.
id
)
return
;
_viewModel
.
selectedProduct
.
value
=
null
;
_viewModel
.
selectedBrand
.
value
=
brand
;
_viewModel
.
getTelcoDetail
();
},
),
);
},
child:
Container
(
padding:
const
EdgeInsets
.
all
(
4
),
height:
48
,
width:
64
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
),
child:
loadNetworkImage
(
url:
_viewModel
.
selectedBrand
.
value
?.
logo
,
fit:
BoxFit
.
fitWidth
,
placeholderAsset:
"assets/images/bg_default_169.png"
,
),
),
),
],
),
const
SizedBox
(
height:
16
),
_buildTagHistory
(),
const
SizedBox
(
height:
8
),
],
),
);
});
}
Widget
_buildTagHistory
()
{
final
histories
=
_viewModel
.
histories
;
return
Obx
(()
{
return
SizedBox
(
height:
36
,
child:
Center
(
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
itemCount:
histories
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
8
),
itemBuilder:
(
_
,
index
)
{
final
phone
=
histories
[
index
];
final
myPhone
=
DataPreference
.
instance
.
phone
??
''
;
final
isMyPhone
=
phone
==
myPhone
;
final
selected
=
phone
==
_viewModel
.
phoneNumber
.
value
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
_viewModel
.
phoneNumber
.
value
=
phone
;
_phoneController
.
text
=
phone
;
_viewModel
.
checkMobileNetwork
();
});
},
child:
Container
(
padding:
EdgeInsets
.
all
(
4
),
decoration:
BoxDecoration
(
color:
selected
?
Colors
.
orange
.
shade50
:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
border:
Border
.
all
(
color:
selected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
),
child:
Center
(
child:
Text
(
isMyPhone
?
" Số của tôi "
:
"
$phone
"
,
textAlign:
TextAlign
.
center
,
style:
TextStyle
(
color:
selected
?
Colors
.
orange
:
Colors
.
black54
,
fontSize:
16
,
fontWeight:
selected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
),
),
),
);
},
),
),
);
});
}
Widget
_buildItemTypeProduct
()
{
return
Expanded
(
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Text
(
"Mệnh giá"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
)),
const
SizedBox
(
height:
12
),
GridView
.
count
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
crossAxisCount:
2
,
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
childAspectRatio:
3
,
children:
_viewModel
.
products
.
value
.
map
((
product
)
{
final
isSelected
=
product
.
id
==
_viewModel
.
selectedProduct
.
value
?.
id
;
final
preview
=
product
.
previewCampaign
;
return
GestureDetector
(
onTap:
()
=>
setState
(()
=>
_viewModel
.
selectedProduct
.
value
=
product
),
child:
Container
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
12
),
border:
Border
.
all
(
color:
isSelected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
color:
isSelected
?
Colors
.
orange
.
withOpacity
(
0.1
)
:
Colors
.
white
,
),
child:
Stack
(
children:
[
// Gift icon
if
(
preview
?.
hasGift
==
true
)
Positioned
(
top:
8
,
left:
0
,
child:
Image
.
asset
(
'assets/images/ic_mark_give_voucher.png'
,
height:
16
),
),
// Point icon
if
((
preview
?.
rewardPoint
??
0
)
>
0
)
Positioned
(
top:
0
,
right:
8
,
child:
Image
.
asset
(
'assets/images/ic_mark_give_point.png'
,
width:
24
),
),
// Text center
Center
(
child:
Text
(
"
${NumberFormat.currency(locale: 'vi_VN', symbol: '', decimalDigits: 0).format(product.amountToBePaid ?? 0)}
đ"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
14
,
color:
isSelected
?
Colors
.
orange
:
Colors
.
black87
,
),
),
),
],
),
),
);
}).
toList
(),
),
],
),
),
);
}
Widget
_buildBottomAction
()
{
final
product
=
_viewModel
.
selectedProduct
.
value
;
final
preview
=
product
?.
previewCampaign
;
final
rewardPoint
=
preview
?.
rewardPoint
??
0
;
final
hasGift
=
preview
?.
hasGift
==
true
;
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
8
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black54
,
blurRadius:
8
,
offset:
Offset
(
0
,
4
))],
),
child:
SafeArea
(
top:
false
,
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
4
),
color:
Colors
.
white
,
child:
Row
(
children:
[
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Row
(
children:
[
Text
(
"Tổng: "
,
style:
TextStyle
(
color:
Colors
.
grey
.
shade600
,
fontSize:
16
)),
Text
(
"
$formattedAmount
đ"
,
style:
const
TextStyle
(
color:
BaseColor
.
primary500
,
fontWeight:
FontWeight
.
bold
,
fontSize:
20
),
),
],
),
const
SizedBox
(
height:
4
),
Row
(
children:
[
if
(
rewardPoint
>
0
)
Row
(
children:
[
Text
(
"+"
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
width:
2
),
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
16
,
height:
16
,
fit:
BoxFit
.
cover
),
const
SizedBox
(
width:
2
),
Text
(
NumberFormat
.
decimalPattern
(
'vi_VN'
).
format
(
rewardPoint
),
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
),
],
),
if
(
rewardPoint
>
0
&&
hasGift
)
const
SizedBox
(
width:
12
),
if
(
hasGift
)
Row
(
children:
[
Text
(
"+"
,
style:
const
TextStyle
(
color:
Colors
.
red
,
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
),
SizedBox
(
width:
2
),
Image
.
asset
(
'assets/images/ic_gift_red.png'
,
width:
16
,
height:
16
,
fit:
BoxFit
.
cover
),
],
),
],
),
],
),
const
Spacer
(),
ElevatedButton
(
onPressed:
()
{
Get
.
toNamed
(
transactionDetailScreen
,
arguments:
{
"product"
:
product
,
"quantity"
:
1
,
"targetPhoneNumber"
:
_viewModel
.
phoneNumber
.
value
},
);
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
)),
),
child:
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
10
),
child:
Text
(
"Nạp ngay"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
),
),
],
),
),
),
);
}
Future
<
void
>
pickContact
(
BuildContext
context
)
async
{
try
{
// Gọi sẽ tự động hiện dialog yêu cầu quyền (nếu cần)
final
Contact
?
contact
=
await
ContactsService
.
openDeviceContactPicker
();
if
(
contact
!=
null
&&
contact
.
phones
!=
null
&&
contact
.
phones
!.
isNotEmpty
)
{
String
phone
=
contact
.
phones
!.
first
.
value
??
''
;
phone
=
phone
.
replaceAll
(
RegExp
(
r'[\s\-\(\)]'
),
''
);
_phoneController
.
text
=
phone
;
_viewModel
.
phoneNumber
.
value
=
phone
;
_viewModel
.
checkMobileNetwork
();
}
else
{
ScaffoldMessenger
.
of
(
context
,
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Không tìm thấy số điện thoại hợp lệ"
)));
}
}
catch
(
e
)
{
print
(
"❌ Lỗi khi truy cập danh bạ:
$e
"
);
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Không thể truy cập danh bạ"
)));
}
}
}
lib/screen/topup/topup_viewmodel.dart
0 → 100644
View file @
fa01087d
This diff is collapsed.
Click to expand it.
lib/screen/transaction/transaction_detail_screen.dart
View file @
fa01087d
...
@@ -25,6 +25,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
...
@@ -25,6 +25,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
final
currencyFormatter
=
NumberFormat
.
currency
(
locale:
'vi_VN'
,
symbol:
'đ'
,
decimalDigits:
0
);
final
currencyFormatter
=
NumberFormat
.
currency
(
locale:
'vi_VN'
,
symbol:
'đ'
,
decimalDigits:
0
);
ProductModel
?
_product
;
ProductModel
?
_product
;
int
_quantity
=
1
;
int
_quantity
=
1
;
String
?
_targetPhoneNumber
;
bool
_isPaymentMethodsExpanded
=
true
;
bool
_isPaymentMethodsExpanded
=
true
;
bool
shouldDisablePaymentMethods
=
false
;
bool
shouldDisablePaymentMethods
=
false
;
...
@@ -35,6 +36,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
...
@@ -35,6 +36,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
if
(
args
is
Map
)
{
if
(
args
is
Map
)
{
_product
=
args
[
'product'
];
_product
=
args
[
'product'
];
_quantity
=
args
[
'quantity'
]
??
1
;
_quantity
=
args
[
'quantity'
]
??
1
;
_targetPhoneNumber
=
args
[
'targetPhoneNumber'
];
}
}
if
(
_product
==
null
)
{
if
(
_product
==
null
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
...
@@ -42,7 +44,9 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
...
@@ -42,7 +44,9 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
});
});
return
;
return
;
}
}
_viewModel
=
Get
.
put
(
TransactionDetailViewModel
(
product:
_product
!,
quantity:
_quantity
));
_viewModel
=
Get
.
put
(
TransactionDetailViewModel
(
product:
_product
!,
quantity:
_quantity
,
targetPhoneNumber:
_targetPhoneNumber
),
);
_viewModel
.
refreshData
();
_viewModel
.
refreshData
();
_viewModel
.
onShowAlertError
=
(
message
)
{
_viewModel
.
onShowAlertError
=
(
message
)
{
...
...
lib/screen/transaction/transaction_detail_viewmodel.dart
View file @
fa01087d
This diff is collapsed.
Click to expand it.
lib/screen/voucher/detail/voucher_detail_screen.dart
View file @
fa01087d
...
@@ -57,10 +57,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
...
@@ -57,10 +57,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
});
});
return
;
return
;
}
}
_viewModel
=
Get
.
put
(
VoucherDetailViewModel
(
_viewModel
=
Get
.
put
(
VoucherDetailViewModel
(
productId:
productId
,
customerProductId:
customerProductId
));
productId:
productId
,
customerProductId:
customerProductId
,
));
_viewModel
.
onShowAlertError
=
(
message
)
{
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
showAlertError
(
content:
message
);
...
@@ -367,7 +364,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
...
@@ -367,7 +364,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
child:
ElevatedButton
(
child:
ElevatedButton
(
onPressed:
()
{
onPressed:
()
{
if
(
_viewModel
.
product
.
value
==
null
)
return
;
if
(
_viewModel
.
product
.
value
==
null
)
return
;
Get
.
to
(()
=>
VoucherCodeCardScreen
(
product:
_viewModel
.
product
.
value
!
,
));
Get
.
to
(()
=>
VoucherCodeCardScreen
(
product:
_viewModel
.
product
.
value
!));
},
},
style:
ElevatedButton
.
styleFrom
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
green
,
backgroundColor:
Colors
.
green
,
...
@@ -442,9 +439,9 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
...
@@ -442,9 +439,9 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
},
},
),
),
),
),
const
SizedBox
(
width:
24
),
],
],
),
),
const
SizedBox
(
width:
36
),
Expanded
(
Expanded
(
child:
SizedBox
(
child:
SizedBox
(
height:
48
,
height:
48
,
...
...
lib/screen/voucher/models/product_brand_model.dart
View file @
fa01087d
This diff is collapsed.
Click to expand it.
lib/screen/voucher/models/product_model.dart
View file @
fa01087d
This diff is collapsed.
Click to expand it.
lib/screen/voucher/models/product_model.g.dart
View file @
fa01087d
This diff is collapsed.
Click to expand it.
lib/screen/voucher/sub_widget/voucher_action_menu.dart
View file @
fa01087d
This diff is collapsed.
Click to expand it.
Prev
1
2
3
4
5
6
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