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
33ec1dde
Commit
33ec1dde
authored
May 23, 2025
by
DatHV
Browse files
update game, notify
parent
8bef97c9
Changes
39
Show whitespace changes
Inline
Side-by-side
lib/screen/transaction/model/order_items_product_payment_response_model.dart
0 → 100644
View file @
33ec1dde
class
OrderItemsProductResponse
{
final
String
?
itemIds
;
final
String
?
itemExpireTime
;
final
String
?
productType
;
OrderItemsProductResponse
({
this
.
itemIds
,
this
.
itemExpireTime
,
this
.
productType
,
});
factory
OrderItemsProductResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
OrderItemsProductResponse
(
itemIds:
json
[
'item_ids'
]
as
String
?,
itemExpireTime:
json
[
'item_expire_time'
]
as
String
?,
productType:
json
[
'product_type'
]
as
String
?,
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'item_ids'
:
itemIds
,
'item_expire_time'
:
itemExpireTime
,
'product_type'
:
productType
,
};
}
lib/screen/transaction/model/order_product_payment_response_model.dart
0 → 100644
View file @
33ec1dde
import
'package:json_annotation/json_annotation.dart'
;
import
'../../voucher/models/product_type.dart'
;
import
'order_items_product_payment_response_model.dart'
;
part
'order_product_payment_response_model.g.dart'
;
@JsonSerializable
()
class
OrderProductPaymentResponseModel
{
final
String
?
id
;
@JsonKey
(
name:
'payment_method'
)
final
String
?
paymentMethod
;
@JsonKey
(
name:
"payment_type"
)
final
String
?
paymentType
;
@JsonKey
(
name:
"payment_partner"
)
final
String
?
paymentPartner
;
@JsonKey
(
name:
"payment_url"
)
final
String
?
paymentUrl
;
@JsonKey
(
name:
"request_id"
)
final
String
?
requestId
;
final
List
<
OrderItemsProductResponse
>?
items
;
final
int
?
subtotal
;
@JsonKey
(
name:
"payment_transaction_id"
)
final
String
?
paymentTransactionId
;
@JsonKey
(
name:
"created_at"
)
final
String
?
createdAt
;
OrderProductPaymentResponseModel
({
this
.
id
,
this
.
paymentMethod
,
this
.
paymentType
,
this
.
paymentPartner
,
this
.
paymentUrl
,
this
.
requestId
,
this
.
items
,
this
.
subtotal
,
this
.
paymentTransactionId
,
this
.
createdAt
,
});
factory
OrderProductPaymentResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$OrderProductPaymentResponseModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$OrderProductPaymentResponseModelToJson
(
this
);
/// Custom Getter: redeemId
String
?
get
redeemId
{
final
firstItemIds
=
items
?.
firstOrNull
?.
itemIds
;
return
firstItemIds
?.
split
(
','
).
first
;
}
/// Custom Getter: expireTime
String
?
get
expireTime
{
return
items
?.
firstOrNull
?.
itemExpireTime
;
}
}
lib/screen/transaction/model/order_product_payment_response_model.g.dart
0 → 100644
View file @
33ec1dde
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'order_product_payment_response_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
OrderProductPaymentResponseModel
_$OrderProductPaymentResponseModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
OrderProductPaymentResponseModel
(
id:
json
[
'id'
]
as
String
?,
paymentMethod:
json
[
'payment_method'
]
as
String
?,
paymentType:
json
[
'payment_type'
]
as
String
?,
paymentPartner:
json
[
'payment_partner'
]
as
String
?,
paymentUrl:
json
[
'payment_url'
]
as
String
?,
requestId:
json
[
'request_id'
]
as
String
?,
items:
(
json
[
'items'
]
as
List
<
dynamic
>?)
?.
map
(
(
e
)
=>
OrderItemsProductResponse
.
fromJson
(
e
as
Map
<
String
,
dynamic
>),
)
.
toList
(),
subtotal:
(
json
[
'subtotal'
]
as
num
?)?.
toInt
(),
paymentTransactionId:
json
[
'payment_transaction_id'
]
as
String
?,
createdAt:
json
[
'created_at'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$OrderProductPaymentResponseModelToJson
(
OrderProductPaymentResponseModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'payment_method'
:
instance
.
paymentMethod
,
'payment_type'
:
instance
.
paymentType
,
'payment_partner'
:
instance
.
paymentPartner
,
'payment_url'
:
instance
.
paymentUrl
,
'request_id'
:
instance
.
requestId
,
'items'
:
instance
.
items
,
'subtotal'
:
instance
.
subtotal
,
'payment_transaction_id'
:
instance
.
paymentTransactionId
,
'created_at'
:
instance
.
createdAt
,
};
lib/screen/transaction/model/payment_method_model.dart
View file @
33ec1dde
...
...
@@ -8,9 +8,10 @@ class PaymentMethodModel {
final
String
?
code
;
final
String
?
name
;
final
String
?
logo
;
@JsonKey
(
name:
'save_token'
)
final
bool
?
saveToken
;
final
bool
?
isSelected
;
final
bool
?
needSaveTokenWhenOrder
;
bool
?
needSaveTokenWhenOrder
;
PaymentMethodType
?
get
type
{
if
(
code
==
null
)
return
null
;
...
...
lib/screen/transaction/model/payment_method_model.g.dart
View file @
33ec1dde
...
...
@@ -12,7 +12,7 @@ PaymentMethodModel _$PaymentMethodModelFromJson(Map<String, dynamic> json) =>
code:
json
[
'code'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
logo:
json
[
'logo'
]
as
String
?,
saveToken:
json
[
'save
T
oken'
]
as
bool
?,
saveToken:
json
[
'save
_t
oken'
]
as
bool
?,
isSelected:
json
[
'isSelected'
]
as
bool
?,
needSaveTokenWhenOrder:
json
[
'needSaveTokenWhenOrder'
]
as
bool
?,
);
...
...
@@ -23,7 +23,7 @@ Map<String, dynamic> _$PaymentMethodModelToJson(PaymentMethodModel instance) =>
'code'
:
instance
.
code
,
'name'
:
instance
.
name
,
'logo'
:
instance
.
logo
,
'save
T
oken'
:
instance
.
saveToken
,
'save
_t
oken'
:
instance
.
saveToken
,
'isSelected'
:
instance
.
isSelected
,
'needSaveTokenWhenOrder'
:
instance
.
needSaveTokenWhenOrder
,
};
lib/screen/transaction/model/payment_method_type.dart
View file @
33ec1dde
...
...
@@ -19,6 +19,19 @@ enum PaymentMethodType {
}
}
String
get
rawValue
{
switch
(
this
)
{
case
PaymentMethodType
.
card
:
return
'CARD'
;
case
PaymentMethodType
.
wallet
:
return
'WALLET'
;
case
PaymentMethodType
.
transfer
:
return
'VA_QRCODE'
;
case
PaymentMethodType
.
internalCard
:
return
'INTERNATIONAL_CARD'
;
}
}
String
get
methodBillEVN
{
switch
(
this
)
{
case
PaymentMethodType
.
card
:
...
...
lib/screen/transaction/model/preview_order_payment_point_data_model.dart
View file @
33ec1dde
...
...
@@ -14,6 +14,8 @@ class PreviewOrderPaymentPointDataModel {
this
.
textDisplay
,
});
bool
get
isEnableUsePoint
=>
status
==
1
;
factory
PreviewOrderPaymentPointDataModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$PreviewOrderPaymentPointDataModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$PreviewOrderPaymentPointDataModelToJson
(
this
);
...
...
lib/screen/transaction/transaction_detail_screen.dart
View file @
33ec1dde
import
'dart:core'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/cupertino.dart'
;
import
'package:get/get.dart'
;
import
'package:cached_network_image/cached_network_image.dart'
;
import
'package:intl/intl.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../widgets/custom_app_bar.dart'
;
import
'../../widgets/dashed_line.dart'
;
import
'../../widgets/image_loader.dart'
;
import
'../voucher/models/product_model.dart'
;
import
'model/payment_bank_account_info_model.dart'
;
import
'model/payment_method_model.dart'
;
import
'model/preview_order_payment_model.dart'
;
import
'model/preview_order_payment_point_data_model.dart'
;
import
'transaction_detail_viewmodel.dart'
;
class
TransactionDetailScreen
extends
StatefulWidget
{
class
TransactionDetailScreen
extends
BaseScreen
{
const
TransactionDetailScreen
({
super
.
key
});
@override
State
<
TransactionDetailScreen
>
createState
()
=>
_TransactionDetailScreenState
();
}
class
_TransactionDetailScreenState
extends
State
<
TransactionDetailScreen
>
{
final
TransactionDetailViewModel
viewModel
=
Get
.
put
(
TransactionDetailViewModel
());
final
currencyFormatter
=
NumberFormat
.
currency
(
locale:
'vi_VN'
,
symbol:
'đ'
,
decimalDigits:
0
,
);
bool
usePoints
=
true
;
int
selectedPaymentMethodIndex
=
-
1
;
bool
isPaymentMethodsExpanded
=
true
;
class
_TransactionDetailScreenState
extends
BaseState
<
TransactionDetailScreen
>
with
BasicState
{
late
final
TransactionDetailViewModel
_viewModel
;
final
currencyFormatter
=
NumberFormat
.
currency
(
locale:
'vi_VN'
,
symbol:
'đ'
,
decimalDigits:
0
);
ProductModel
?
_product
;
int
_quantity
=
1
;
bool
_isPaymentMethodsExpanded
=
true
;
bool
shouldDisablePaymentMethods
=
false
;
@override
void
initState
()
{
super
.
initState
();
viewModel
.
refreshData
();
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
_product
=
args
[
'product'
];
_quantity
=
args
[
'quantity'
]
??
1
;
}
if
(
_product
==
null
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
return
;
}
_viewModel
=
Get
.
put
(
TransactionDetailViewModel
(
product:
_product
!,
quantity:
_quantity
));
_viewModel
.
refreshData
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
}
@override
Widget
build
(
BuildContext
context
)
{
Widget
createBody
(
)
{
return
Scaffold
(
backgroundColor:
Colors
.
grey
.
shade50
,
appBar:
CustomAppBar
.
back
(
title:
"Thông tin thanh toán"
),
body:
Obx
(()
{
if
(
viewModel
.
isLoading
.
value
)
{
if
(
_
viewModel
.
isLoading
.
value
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
final
previewData
=
viewModel
.
previewData
.
value
;
final
previewData
=
_viewModel
.
previewData
.
value
;
if
(
previewData
==
null
)
{
return
const
Center
(
child:
Text
(
'Không có dữ liệu'
));
}
final
totalPrice
=
previewData
.
totalPrice
??
0
;
final
pointValue
=
previewData
.
pointData
?.
point
??
0
;
final
finalTotal
=
usePoints
&&
previewData
.
pointData
?.
status
==
1
?
totalPrice
-
pointValue
:
totalPrice
;
shouldDisablePaymentMethods
=
_viewModel
.
usePoints
.
value
&&
(
_viewModel
.
finalTotal
<=
0
);
return
Column
(
children:
[
Expanded
(
...
...
@@ -63,20 +75,16 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildSectionHeader
(
'Chi tiết giao dịch'
),
_buildProductInfoSection
(
previewData
),
if
(
previewData
.
pointData
!=
null
&&
previewData
.
pointData
!.
status
==
1
)
_buildPointToggleSection
(
previewData
.
pointData
!),
_buildTotalSection
(
totalPrice
,
usePoints
?
pointValue
:
0
,
finalTotal
),
if
(
previewData
.
pointData
!=
null
)
_buildPointToggleSection
(),
_buildTotalSection
(
totalPrice
,
_viewModel
.
usePoints
.
value
?
pointValue
:
0
,
_viewModel
.
finalTotal
),
_buildSavedCardsSection
(),
_buildPaymentMethodsSection
(),
const
SizedBox
(
height:
100
),
// Khoảng trống để không bị che bởi bottom bar
],
),
),
),
// Bottom bar với tổng thanh toán và nút tiếp tục
_buildBottomBar
(
finalTotal
),
_buildBottomBar
(),
],
);
}),
...
...
@@ -84,15 +92,12 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
}
Widget
_buildSectionHeader
(
String
title
)
{
return
Padding
(
return
Container
(
color:
Colors
.
grey
.
shade50
,
width:
double
.
infinity
,
child:
Padding
(
padding:
const
EdgeInsets
.
fromLTRB
(
16
,
16
,
16
,
8
),
child:
Text
(
title
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
,
),
child:
Text
(
title
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
)),
),
);
}
...
...
@@ -103,35 +108,34 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
return
Container
(
color:
Colors
.
white
,
child:
Column
(
children:
productInfo
.
map
((
item
)
{
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildSectionHeader
(
'Chi tiết giao dịch'
),
Column
(
children:
productInfo
.
map
((
item
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
item
.
name
??
''
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
grey
.
shade700
,
),
),
Text
(
item
.
value
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
),
),
Text
(
item
.
name
??
''
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
grey
.
shade700
)),
Text
(
item
.
value
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
)),
],
),
);
}).
toList
(),
),
],
),
);
}
Widget
_buildPointToggleSection
(
PreviewOrderPaymentPointDataModel
pointData
)
{
Widget
_buildPointToggleSection
()
{
final
pointData
=
_viewModel
.
previewData
.
value
?.
pointData
;
if
(
pointData
==
null
)
{
return
const
SizedBox
.
shrink
();
}
return
Container
(
color:
Colors
.
white
,
margin:
const
EdgeInsets
.
only
(
top:
8
),
...
...
@@ -141,25 +145,31 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
children:
[
Row
(
children:
[
Center
(
child:
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
30
,
height:
30
),
),
Center
(
child:
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
30
,
height:
30
)),
const
SizedBox
(
width:
12
),
Text
(
pointData
.
textDisplay
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
),
),
Text
(
pointData
!.
textDisplay
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
)),
],
),
CupertinoSwitch
(
value:
usePoints
,
activeColor:
Colors
.
green
,
if
(
pointData
!.
isEnableUsePoint
)
Switch
(
activeColor:
Colors
.
white
,
activeTrackColor:
Colors
.
green
,
inactiveThumbColor:
Colors
.
white
,
inactiveTrackColor:
Colors
.
grey
.
shade400
,
value:
_viewModel
.
usePoints
.
value
,
onChanged:
(
value
)
{
setState
(()
{
usePoints
=
value
;
_viewModel
.
usePoints
.
value
=
value
;
final
total
=
_viewModel
.
previewData
.
value
?.
totalPrice
??
0
;
final
point
=
pointData
!.
point
??
0
;
shouldDisablePaymentMethods
=
value
&&
(
total
-
point
<=
0
);
if
(!
value
&&
_viewModel
.
selectedPaymentMethodIndex
<
0
)
{
if
(
_viewModel
.
paymentBankAccounts
.
value
.
isNotEmpty
)
{
_viewModel
.
selectedPaymentMethodIndex
=
_viewModel
.
definedCodeIndexBankAccount
;
}
else
if
(
_viewModel
.
paymentMethods
.
value
.
isNotEmpty
)
{
_viewModel
.
selectedPaymentMethodIndex
=
0
;
}
}
});
},
),
...
...
@@ -176,9 +186,8 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
child:
Column
(
children:
[
_buildTotalRow
(
'Tổng số tiền'
,
totalPrice
,
false
),
if
(
pointsUsed
>
0
)
_buildTotalRow
(
'Sử dụng điểm'
,
-
pointsUsed
,
false
),
_buildTotalRow
(
'Tổng tạm tính'
,
finalTotal
,
tru
e
),
_buildTotalRow
(
'Tổng tạm tính'
,
finalTotal
,
fals
e
),
],
),
);
...
...
@@ -193,13 +202,7 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
label
,
style:
TextStyle
(
fontSize:
16
,
color:
isHighlighted
?
Colors
.
black87
:
Colors
.
grey
.
shade700
,
),
),
Text
(
label
,
style:
TextStyle
(
fontSize:
16
,
color:
isHighlighted
?
Colors
.
black87
:
Colors
.
grey
.
shade700
)),
Text
(
displayAmount
,
style:
TextStyle
(
...
...
@@ -215,23 +218,29 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
Widget
_buildSavedCardsSection
()
{
return
Obx
(()
{
final
bankAccounts
=
viewModel
.
paymentBankAccounts
;
final
bankAccounts
=
_viewModel
.
paymentBankAccounts
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildSectionHeader
(
'Tài khoản/Thẻ đã lưu'
),
Container
(
IgnorePointer
(
ignoring:
shouldDisablePaymentMethods
,
child:
Opacity
(
opacity:
shouldDisablePaymentMethods
?
0.6
:
1.0
,
child:
Container
(
color:
Colors
.
white
,
child:
bankAccounts
.
isEmpty
child:
bankAccounts
.
isEmpty
?
_buildNoSavedCardsItem
()
:
Column
(
:
Row
(
children:
List
.
generate
(
bankAccounts
.
length
,
(
index
)
=>
_buildSavedCardItem
(
bankAccounts
[
index
],
index
),
),
),
),
),
),
],
);
});
...
...
@@ -245,47 +254,32 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
Container
(
width:
40
,
height:
40
,
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
,
borderRadius:
BorderRadius
.
circular
(
4
),
),
child:
const
Center
(
child:
Icon
(
Icons
.
credit_card
,
color:
Colors
.
grey
,
),
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
,
borderRadius:
BorderRadius
.
circular
(
4
)),
child:
const
Center
(
child:
Icon
(
Icons
.
credit_card
,
color:
Colors
.
grey
)),
),
const
SizedBox
(
width:
12
),
const
Text
(
'Không có thẻ đã lưu'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
),
),
const
Text
(
'Không có thẻ đã lưu'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
)),
],
),
);
}
Widget
_buildSavedCardItem
(
PaymentBankAccountInfoModel
account
,
int
index
)
{
final
isSelected
=
selectedPaymentMethodIndex
==
-
1000
-
index
;
final
isSelected
=
_viewModel
.
selectedPaymentMethodIndex
==
_viewModel
.
definedCodeIndexBankAccount
+
index
;
return
InkWell
(
onTap:
()
{
setState
(()
{
selectedPaymentMethodIndex
=
-
1000
-
index
;
// tránh đụng index method
_viewModel
.
selectedPaymentMethodIndex
=
_viewModel
.
definedCodeIndexBankAccount
+
index
;
// tránh đụng index method
});
},
child:
IntrinsicWidth
(
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical
:
8
),
margin:
const
EdgeInsets
.
only
(
left:
16
,
right:
0
,
top:
8
,
bottom
:
8
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
border:
Border
.
all
(
color:
isSelected
?
Colors
.
red
:
Colors
.
grey
.
shade300
,
width:
1.5
,
),
border:
Border
.
all
(
color:
isSelected
?
Colors
.
red
:
Colors
.
grey
.
shade300
,
width:
1.5
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Row
(
...
...
@@ -293,34 +287,18 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
if
(
account
.
bankLogo
!=
null
)
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
4
),
child:
CachedNetworkImage
(
imageUrl:
account
.
bankLogo
!,
width:
32
,
height:
32
,
placeholder:
(
context
,
url
)
=>
Container
(
child:
loadNetworkImage
(
url:
account
.
bankLogo
,
width:
32
,
height:
32
,
color:
Colors
.
grey
.
shade200
,
),
errorWidget:
(
context
,
url
,
error
)
=>
Container
(
width:
32
,
height:
32
,
color:
Colors
.
grey
.
shade200
,
child:
const
Icon
(
Icons
.
error
,
size:
16
),
),
placeholderAsset:
'assets/images/ic_logo.png'
,
),
),
const
SizedBox
(
width:
12
),
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
account
.
bankName
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
),
),
Text
(
account
.
bankShortName
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
)),
if
(
account
.
cardNumber
!=
null
)
Text
(
'****
${account.cardNumber!.substring(account.cardNumber!.length - 4)}
'
,
...
...
@@ -331,13 +309,13 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
],
),
),
),
);
}
Widget
_buildPaymentMethodsSection
()
{
return
Obx
(()
{
final
methods
=
viewModel
.
paymentMethods
;
final
methods
=
_viewModel
.
paymentMethods
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
...
...
@@ -346,35 +324,37 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
const
Text
(
'Phương thức thanh toán khác'
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
),
),
),
IconButton
(
icon:
Icon
(
isPaymentMethodsExpanded
?
Icons
.
keyboard_arrow_up
:
Icons
.
keyboard_arrow_down
,
_
isPaymentMethodsExpanded
?
Icons
.
keyboard_arrow_up
:
Icons
.
keyboard_arrow_down
,
color:
Colors
.
grey
,
),
onPressed:
()
{
setState
(()
{
isPaymentMethodsExpanded
=
!
isPaymentMethodsExpanded
;
_
isPaymentMethodsExpanded
=
!
_
isPaymentMethodsExpanded
;
});
},
),
],
),
),
if
(
isPaymentMethodsExpanded
)
Container
(
if
(
_isPaymentMethodsExpanded
)
IgnorePointer
(
ignoring:
shouldDisablePaymentMethods
,
child:
Opacity
(
opacity:
shouldDisablePaymentMethods
?
0.6
:
1.0
,
child:
Container
(
color:
Colors
.
white
,
child:
Column
(
children:
List
.
generate
(
methods
.
length
,
(
index
)
=>
_buildPaymentMethodItem
(
methods
[
index
],
index
),
children:
List
.
generate
(
methods
.
length
,
(
index
)
=>
_buildPaymentMethodItem
(
methods
[
index
],
index
)),
),
),
),
),
...
...
@@ -384,95 +364,83 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
}
Widget
_buildPaymentMethodItem
(
PaymentMethodModel
method
,
int
index
)
{
final
isSelected
=
selectedPaymentMethodIndex
==
index
;
return
InkWell
(
onTap:
()
{
setState
(()
{
selectedPaymentMethodIndex
=
index
;
});
},
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
4
,
vertical:
4
),
decoration:
BoxDecoration
(
border:
index
<
viewModel
.
paymentMethods
.
length
-
1
?
Border
(
bottom:
BorderSide
(
color:
Colors
.
grey
.
shade200
,
width:
0.5
))
:
null
,
),
child:
Row
(
final
isSelected
=
_viewModel
.
selectedPaymentMethodIndex
==
index
;
final
canSaveToken
=
method
.
saveToken
==
true
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Radio
<
int
>(
RadioListTile
<
int
>(
activeColor:
BaseColor
.
primary400
,
value:
index
,
groupValue:
selectedPaymentMethodIndex
,
onChanged:
(
val
ue
)
{
groupValue:
_viewModel
.
selectedPaymentMethodIndex
,
onChanged:
(
val
)
{
setState
(()
{
selectedPaymentMethodIndex
=
val
ue
!;
_viewModel
.
selectedPaymentMethodIndex
=
val
!;
});
},
activeColor:
Colors
.
red
,
),
if
(
method
.
logo
!=
null
&&
method
.
logo
!.
isNotEmpty
)
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
4
),
child:
CachedNetworkImage
(
imageUrl:
method
.
logo
!,
width:
32
,
height:
32
,
placeholder:
(
context
,
url
)
=>
Container
(
width:
32
,
height:
32
,
color:
Colors
.
grey
.
shade200
,
),
errorWidget:
(
context
,
url
,
error
)
=>
Container
(
width:
32
,
height:
32
,
color:
Colors
.
grey
.
shade200
,
child:
const
Icon
(
Icons
.
error
,
size:
16
),
contentPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
title:
Row
(
children:
[
if
(
method
.
logo
!=
null
)
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
8
),
child:
Image
.
network
(
method
.
logo
!,
width:
24
,
height:
24
,
errorBuilder:
(
_
,
__
,
___
)
=>
const
Icon
(
Icons
.
payment
),
),
),
Text
(
method
.
name
??
''
,
style:
const
TextStyle
(
fontSize:
16
)),
],
),
const
SizedBox
(
width:
12
),
Text
(
method
.
name
??
''
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
),
if
(
isSelected
&&
canSaveToken
)
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
48
,
right:
16
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
"Lưu lại thanh toán sau"
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
)),
Switch
(
activeColor:
Colors
.
white
,
activeTrackColor:
Colors
.
green
,
inactiveThumbColor:
Colors
.
white
,
inactiveTrackColor:
Colors
.
grey
.
shade400
,
value:
method
.
needSaveTokenWhenOrder
??
true
,
onChanged:
(
val
)
{
setState
(()
{
method
.
needSaveTokenWhenOrder
=
val
;
});
},
),
],
),
),
Container
(
height:
1
,
color:
Colors
.
grey
.
shade200
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
)),
],
);
}
Widget
_buildBottomBar
(
int
finalTotal
)
{
return
Container
(
Widget
_buildBottomBar
()
{
return
SafeArea
(
top:
false
,
minimum:
EdgeInsets
.
only
(
bottom:
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
),
child:
Container
(
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
10
,
offset:
const
Offset
(
0
,
-
5
),
),
],
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
10
,
offset:
const
Offset
(
0
,
-
5
))],
),
child:
Column
(
children:
[
if
((
viewModel
.
previewData
.
value
?.
feeNote
??
""
).
isNotEmpty
)
if
((
_
viewModel
.
previewData
.
value
?.
feeNote
??
""
).
isNotEmpty
)
Text
(
viewModel
.
previewData
.
value
?.
feeNote
??
''
,
style:
TextStyle
(
fontSize:
12
,
color:
Colors
.
orange
.
shade700
,
),
_viewModel
.
previewData
.
value
?.
feeNote
??
''
,
style:
TextStyle
(
fontSize:
12
,
color:
Colors
.
orange
.
shade700
),
),
const
SizedBox
(
height:
12
),
Container
(
height:
1
,
color:
Colors
.
grey
.
shade200
,
),
Container
(
height:
1
,
color:
Colors
.
grey
.
shade200
),
const
SizedBox
(
height:
12
),
Row
(
children:
[
...
...
@@ -481,55 +449,35 @@ class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
const
Text
(
'Tổng thanh toán'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
grey
,
),
),
const
Text
(
'Tổng thanh toán'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
grey
)),
const
SizedBox
(
height:
4
),
Text
(
currencyFormatter
.
format
(
finalTotal
),
style:
const
TextStyle
(
fontSize:
22
,
fontWeight:
FontWeight
.
bold
,
),
currencyFormatter
.
format
(
_viewModel
.
finalTotal
),
style:
const
TextStyle
(
fontSize:
22
,
fontWeight:
FontWeight
.
bold
),
),
],
),
),
ElevatedButton
(
onPressed:
selectedPaymentMethodIndex
>=
0
?
()
{
// Xử lý khi nhấn nút tiếp tục
final
selectedMethod
=
viewModel
.
paymentMethods
[
selectedPaymentMethodIndex
];
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'Đang xử lý thanh toán với
${selectedMethod.name}
...'
)),
);
}
:
null
,
onPressed:
_viewModel
.
finalTotal
>
0
&&
_viewModel
.
selectedPaymentMethodIndex
<
0
?
null
:
_viewModel
.
requestPaymentProduct
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Color
s
.
red
,
backgroundColor:
Base
Color
.
primary500
,
foregroundColor:
Colors
.
white
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
32
,
vertical:
16
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
disabledBackgroundColor:
Colors
.
red
.
withOpacity
(
0.6
),
disabledForegroundColor:
Colors
.
white
.
withOpacity
(
0.8
),
),
child:
const
Text
(
'Tiếp tục'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
),
),
child:
const
Text
(
'Tiếp tục'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
)),
),
],
),
const
SizedBox
(
height:
44
),
//
const SizedBox(height: 44),
],
),
),
);
}
}
lib/screen/transaction/transaction_detail_viewmodel.dart
View file @
33ec1dde
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'package:uuid/uuid.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../configs/constants.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../voucher/models/product_model.dart'
;
import
'../webview/payment_web_view_screen.dart'
;
import
'../webview/web_view_screen.dart'
;
import
'model/payment_bank_account_info_model.dart'
;
import
'model/payment_method_model.dart'
;
import
'model/preview_order_payment_model.dart'
;
class
TransactionDetailViewModel
extends
RestfulApiViewModel
{
final
int
definedCodeIndexBankAccount
=
1000
;
var
previewData
=
Rxn
<
PreviewOrderPaymentModel
>();
var
paymentMethods
=
RxList
<
PaymentMethodModel
>();
var
paymentBankAccounts
=
RxList
<
PaymentBankAccountInfoModel
>();
final
RxBool
isLoading
=
false
.
obs
;
final
ProductModel
product
;
final
int
quantity
;
final
RxBool
usePoints
=
true
.
obs
;
var
selectedPaymentMethodIndex
=
-
1
.
obs
;
void
Function
(
String
message
)?
onShowAlertError
;
int
get
finalTotal
{
final
totalPrice
=
previewData
.
value
?.
totalPrice
??
0
;
final
pointValue
=
previewData
.
value
?.
pointData
?.
point
??
0
;
final
finalTotal
=
usePoints
.
value
&&
previewData
.
value
?.
pointData
?.
status
==
1
?
totalPrice
-
pointValue
:
totalPrice
;
return
finalTotal
;
}
TransactionDetailViewModel
({
required
this
.
product
,
required
this
.
quantity
});
@override
void
onInit
()
{
...
...
@@ -20,23 +45,80 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
Future
<
void
>
refreshData
()
async
{
isLoading
.
value
=
true
;
await
Future
.
wait
([
_getPreviewOrderPayment
(),
_getPaymentMethods
(),
_getPaymentBankAccounts
(),
]);
await
Future
.
wait
([
_getPreviewOrderPayment
(),
_getPaymentMethods
(),
_getPaymentBankAccounts
()]);
isLoading
.
value
=
false
;
}
requestPaymentProduct
()
{
showLoading
();
final
requestId
=
Uuid
().
v4
();
int
?
point
=
usePoints
.
value
?
previewData
.
value
?.
pointData
?.
point
:
0
;
PaymentBankAccountInfoModel
?
selectedBankAccount
;
PaymentMethodModel
?
selectedPaymentMethod
;
bool
?
saveToken
;
if
(
finalTotal
==
0
)
{
point
=
previewData
.
value
?.
pointData
?.
point
??
0
;
}
else
if
(
selectedPaymentMethodIndex
>=
definedCodeIndexBankAccount
)
{
selectedBankAccount
=
paymentBankAccounts
.
value
[
selectedPaymentMethodIndex
-
definedCodeIndexBankAccount
];
}
else
if
(
selectedPaymentMethodIndex
>=
0
)
{
selectedPaymentMethod
=
paymentMethods
.
value
[
selectedPaymentMethodIndex
];
saveToken
=
selectedPaymentMethod
?.
saveToken
==
true
&&
selectedPaymentMethod
?.
needSaveTokenWhenOrder
==
true
;
}
client
.
orderSubmitPayment
(
products:
[
product
],
quantity:
quantity
,
requestId:
requestId
,
point:
point
,
cash:
finalTotal
,
method:
selectedPaymentMethod
,
paymentTokenId:
selectedBankAccount
?.
id
,
saveToken:
saveToken
,
metadata:
""
,
)
.
then
((
value
)
{
hideLoading
();
if
(
value
.
isSuccess
)
{
final
data
=
value
.
data
;
if
((
data
?.
paymentUrl
??
""
).
isNotEmpty
)
{
Get
.
toNamed
(
paymentWebViewScreen
,
arguments:
PaymentWebViewInput
(
url:
data
?.
paymentUrl
??
""
,
isContract:
false
,
orderId:
data
?.
id
??
""
,
showAlertBack:
true
,
callback:
(
result
)
{},
)
);
}
else
if
((
data
?.
redeemId
??
""
).
isNotEmpty
&&
(
data
?.
id
??
""
).
isNotEmpty
)
{
Get
.
offNamed
(
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
data
?.
id
??
""
,
"canBack"
:
true
,
}
);
}
else
{
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
}
}
else
{
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
}
});
}
Future
<
void
>
_getPreviewOrderPayment
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
try
{
final
body
=
{
"product_id"
:
13796
,
"quantity"
:
1
,
"product_id"
:
product
.
id
,
"quantity"
:
quantity
,
"price"
:
product
.
amountToBePaid
??
0
,
"access_token"
:
token
,
"price"
:
100000
,
};
if
(
product
.
previewFlashSale
?.
isFlashSalePrice
==
true
&&
product
.
previewFlashSale
?.
id
!=
null
)
{
body
[
"flash_sale_id"
]
=
product
.
previewFlashSale
!.
id
;
}
final
response
=
await
client
.
getPreviewOrderInfo
(
body
);
previewData
.
value
=
response
.
data
;
}
catch
(
error
)
{
...
...
lib/screen/voucher/detail/voucher_detail_screen.dart
View file @
33ec1dde
import
'dart:math'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_widget_from_html/flutter_widget_from_html.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/models/product_type.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../../base/base_screen.dart'
;
import
'../../../base/basic_state.dart'
;
import
'../../../preference/point/point_manager.dart'
;
import
'../../../resouce/base_color.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../widgets/alert/data_alert_model.dart'
;
import
'../../../widgets/back_button.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_point_text_tag.dart'
;
...
...
@@ -181,12 +185,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
product
.
brand
?.
name
??
''
,
style:
const
TextStyle
(
fontSize:
14
))),
// PriceTagWidget(point: product.amountToBePaid ?? 0),
CustomPointText
(
point:
product
.
amountToBePaid
??
0
,
type:
product
.
price
?.
method
,
),
CustomPointText
(
point:
product
.
amountToBePaid
??
0
,
type:
product
.
price
?.
method
),
],
),
],
),
...
...
@@ -203,8 +203,10 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
if
(
hasExpire
)
Text
(
'Hạn dùng: '
,
style:
const
TextStyle
(
color:
Colors
.
grey
,
fontWeight:
FontWeight
.
bold
,
fontSize:
12
)),
if
(
hasExpire
)
Text
(
product
.
expired
?
"Hết hạn"
:
product
.
expire
,
style:
const
TextStyle
(
color:
BaseColor
.
primary500
,
fontWeight:
FontWeight
.
bold
,
fontSize:
12
)),
Text
(
product
.
expired
?
"Hết hạn"
:
product
.
expire
,
style:
const
TextStyle
(
color:
BaseColor
.
primary500
,
fontWeight:
FontWeight
.
bold
,
fontSize:
12
),
),
if
(
isOutOfStock
)
Container
(
margin:
const
EdgeInsets
.
only
(
left:
8
),
...
...
@@ -219,14 +221,6 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
);
}
Widget
_buildDetailBlock
(
ProductModel
product
)
{
return
_buildTextBlock
(
"Chi tiết ưu đãi:"
,
product
.
content
?.
detail
);
}
Widget
_buildConditionBlock
(
ProductModel
product
)
{
return
_buildTextBlock
(
"Điều kiện áp dụng:"
,
product
.
content
?.
termAndCondition
);
}
Widget
_buildTextBlock
(
String
title
,
String
?
content
)
{
if
(
content
==
null
||
content
.
isEmpty
)
return
const
SizedBox
();
return
Container
(
...
...
@@ -379,8 +373,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height:
48
,
child:
ElevatedButton
(
onPressed:
()
{
Get
.
toNamed
(
registerFormInputScreen
,
arguments:
{
"id"
:
13484
});
// TODO: Handle đổi ưu đãi
_handleContinueButtonAction
();
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
...
...
@@ -399,6 +392,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
return
_buildBottomActionContainer
(
child:
Row
(
children:
[
if
(
_viewModel
.
product
.
value
?.
productType
==
ProductType
.
voucher
)
Row
(
children:
[
Container
(
...
...
@@ -440,7 +434,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height:
48
,
child:
ElevatedButton
(
onPressed:
()
{
_
viewModel
.
verifyOrderProduct
();
_
handleContinueButtonAction
();
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
...
...
@@ -458,6 +452,50 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
);
}
_handleContinueButtonAction
()
{
final
product
=
_viewModel
.
product
.
value
;
if
(
product
?.
requireFormRegis
==
true
)
{
Get
.
toNamed
(
registerFormInputScreen
,
arguments:
{
"product"
:
product
});
return
;
}
_viewModel
.
verifyOrderProduct
(()
{
if
(
product
?.
price
?.
method
==
CashType
.
point
)
{
_handleRedeemProduct
();
}
else
{
Get
.
toNamed
(
transactionDetailScreen
,
arguments:
{
"product"
:
product
,
"quantity"
:
_viewModel
.
quantity
.
value
});
}
});
}
_handleRedeemProduct
()
{
final
amountToBePaid
=
_viewModel
.
product
.
value
?.
amountToBePaid
??
0
;
if
(
UserPointManager
().
point
<
amountToBePaid
)
{
showAlertError
(
content:
"Bạn không đủ điểm để đổi ưu đãi này"
);
return
;
}
final
dataAlert
=
DataAlertModel
(
title:
"Xác nhận"
,
description:
"Bạn có muốn sử dụng <b style=
\"
color:#E71D28
\"
>
${amountToBePaid.money(CurrencyUnit.point)}
</b> MyPoint để đổi Ưu Đãi này không?"
,
localHeaderImage:
"assets/images/ic_pipi_02.png"
,
buttons:
[
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
{
Get
.
back
();
_viewModel
.
redeemProduct
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Huỷ"
,
onPressed:
()
=>
Get
.
back
(),
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
,
),],
);
showAlert
(
data:
dataAlert
);
}
Widget
_buildFavoriteButton
()
{
return
Obx
(()
{
final
isFavorite
=
_viewModel
.
liked
.
value
;
...
...
lib/screen/voucher/detail/voucher_detail_viewmodel.dart
View file @
33ec1dde
import
'dart:ui'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:uuid/uuid.dart'
;
import
'../../../base/restful_api_viewmodel.dart'
;
import
'../../../configs/constants.dart'
;
import
'../../../shared/router_gage.dart'
;
...
...
@@ -38,7 +41,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
}
}
catch
(
error
)
{
onShowAlertError
?.
call
(
"Error toggling favorite:
$error
"
);
print
(
"Error toggling favorite:
$error
"
);
}
}
...
...
@@ -51,7 +53,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
liked
.
value
=
product
.
value
?.
liked
==
true
;
}
catch
(
error
)
{
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
print
(
"Error fetching product detail:
$error
"
);
}
finally
{
isLoading
.
value
=
false
;
}
...
...
@@ -67,13 +68,9 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
}
finally
{}
}
verifyOrderProduct
()
async
{
verifyOrderProduct
(
Function
verified
)
async
{
final
value
=
product
.
value
;
var
body
=
{
"product_id"
:
productId
,
"price"
:
value
?.
amountToBePaid
,
"quantity"
:
quantity
.
value
,
};
var
body
=
{
"product_id"
:
productId
,
"price"
:
value
?.
amountToBePaid
,
"quantity"
:
quantity
.
value
};
if
(
value
?.
previewFlashSale
?.
isFlashSalePrice
==
true
)
{
final
flashSaleId
=
value
?.
previewFlashSale
?.
id
;
if
(
flashSaleId
!=
null
)
{
...
...
@@ -86,8 +83,28 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
if
(!
value
.
isSuccess
)
{
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
}
else
{
Get
.
toNamed
(
transactionDetailScreen
);
// onShowAlertError?.call("Verify Order Product Success -> Go To Payment Detail");
verified
.
call
();
}
});
}
redeemProduct
()
{
showLoading
();
final
requestId
=
Uuid
().
v4
();
client
.
orderSubmitPayment
(
products:
[
product
.
value
!],
quantity:
1
,
requestId:
requestId
,
point:
product
.
value
?.
amountToBePaid
??
0
,
)
.
then
((
value
)
{
hideLoading
();
if
(!
value
.
isSuccess
)
{
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
}
else
{
// Success -> go to transaction detail screen
onShowAlertError
?.
call
(
"Redeem success -> go to transaction detail screen"
);
}
});
}
...
...
lib/screen/voucher/models/product_model.dart
View file @
33ec1dde
...
...
@@ -9,6 +9,7 @@ import 'package:mypoint_flutter_app/screen/voucher/models/product_item_model.dar
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
'package:mypoint_flutter_app/screen/voucher/models/product_type.dart'
;
import
'../../flash_sale/preview_flash_sale_model.dart'
;
import
'media_type.dart'
;
import
'my_product_status_type.dart'
;
...
...
@@ -35,6 +36,9 @@ class ProductModel {
final
ProductItemModel
?
itemModel
;
@JsonKey
(
name:
'expire_time'
)
final
String
?
expireTime
;
@JsonKey
(
name:
'require_form_regis'
)
final
bool
?
requireFormRegis
;
final
String
?
type
;
ProductModel
({
this
.
id
,
...
...
@@ -49,12 +53,18 @@ class ProductModel {
this
.
customerInfoModel
,
this
.
itemModel
,
this
.
expireTime
,
this
.
requireFormRegis
,
this
.
type
,
});
String
?
get
name
{
return
content
?.
name
;
}
ProductType
get
productType
{
return
ProductTypeExt
.
from
(
type
)
??
ProductType
.
voucher
;
}
String
get
expire
{
final
ex
=
(
isMyProduct
?
itemModel
?.
expireTime
:
expireTime
)
??
""
;
return
ex
.
toDate
()?.
toFormattedString
()
??
""
;
...
...
lib/screen/voucher/models/product_model.g.dart
View file @
33ec1dde
...
...
@@ -53,6 +53,8 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
json
[
'product_item'
]
as
Map
<
String
,
dynamic
>,
),
expireTime:
json
[
'expire_time'
]
as
String
?,
requireFormRegis:
json
[
'require_form_regis'
]
as
bool
?,
type:
json
[
'type'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$ProductModelToJson
(
ProductModel
instance
)
=>
...
...
@@ -69,4 +71,6 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
'customer_product_info'
:
instance
.
customerInfoModel
,
'product_item'
:
instance
.
itemModel
,
'expire_time'
:
instance
.
expireTime
,
'require_form_regis'
:
instance
.
requireFormRegis
,
'type'
:
instance
.
type
,
};
lib/screen/voucher/voucher_list/voucher_list_screen.dart
View file @
33ec1dde
...
...
@@ -4,6 +4,7 @@ import '../../../shared/router_gage.dart';
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_navigation_bar.dart'
;
import
'../../../widgets/custom_search_navigation_bar.dart'
;
import
'../../transaction/history/transaction_history_detail_screen.dart'
;
import
'../sub_widget/voucher_item_list.dart'
;
import
'voucher_list_viewmodel.dart'
;
...
...
@@ -82,7 +83,12 @@ class _VoucherListScreenState extends State<VoucherListScreen> {
final
product
=
_viewModel
.
products
[
index
];
return
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
voucherDetailScreen
,
arguments:
product
.
id
);
// // TODO
Get
.
toNamed
(
transactionHistoryDetailScreen
,
arguments:
{
'orderId'
:
"02744757-a5ef-420d-a737-c0bc93d767b7"
,
'canBack'
:
true
,
});
// Get.toNamed(voucherDetailScreen, arguments: product.id);
},
child:
VoucherListItem
(
product:
product
),
);
...
...
lib/screen/webview/payment_web_view_screen.dart
0 → 100644
View file @
33ec1dde
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_app_bar.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/back_button.dart'
;
enum
PaymentProcess
{
begin
,
processing
,
success
,
failure
;
String
get
title
{
switch
(
this
)
{
case
PaymentProcess
.
begin
:
return
'Bắt đầu thanh toán'
;
case
PaymentProcess
.
processing
:
return
'Đang xử lý thanh toán'
;
case
PaymentProcess
.
success
:
return
'Thanh toán thành công'
;
case
PaymentProcess
.
failure
:
return
'Thanh toán thất bại'
;
}
}
String
get
content
{
switch
(
this
)
{
case
PaymentProcess
.
begin
:
return
'Vui lòng tiến hành thanh toán.'
;
case
PaymentProcess
.
processing
:
return
'Hệ thống đang xử lý giao dịch của bạn.'
;
case
PaymentProcess
.
success
:
return
'Giao dịch của bạn đã hoàn tất.'
;
case
PaymentProcess
.
failure
:
return
'Giao dịch thất bại. Vui lòng thử lại.'
;
}
}
}
class
PaymentWebViewInput
{
final
String
url
;
final
String
orderId
;
final
bool
isContract
;
final
bool
showAlertBack
;
final
Function
(
PaymentProcess
result
)?
callback
;
PaymentWebViewInput
({
required
this
.
url
,
required
this
.
orderId
,
this
.
isContract
=
false
,
this
.
showAlertBack
=
true
,
this
.
callback
,
});
}
class
PaymentWebViewScreen
extends
BaseScreen
{
const
PaymentWebViewScreen
({
super
.
key
});
@override
State
<
PaymentWebViewScreen
>
createState
()
=>
_PaymentWebViewScreenState
();
}
class
_PaymentWebViewScreenState
extends
BaseState
<
PaymentWebViewScreen
>
with
BasicState
{
late
final
PaymentWebViewInput
input
;
late
final
WebViewController
_controller
;
bool
_isLoading
=
true
;
final
List
<
String
>
paymentSuccessUrls
=
[
"https://localhost/paymentGatewayRequestSuccessful"
,
"mypointapp://open?click_action_type=PAYMENT_SUCCESS"
,
"https://localhost/paymentGatewayAutoDebitRequestSuccessful"
,
];
final
List
<
String
>
paymentFailedUrls
=
[
"https://localhost/paymentGatewayRequestFailed"
,
"mypointapp://open?click_action_type=PAYMENT_FAIL"
,
"https://localhost/paymentGatewayAutoDebitRequestFailed"
,
];
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
!
PaymentWebViewInput
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
return
;
}
input
=
args
;
_controller
=
WebViewController
()
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
..
setNavigationDelegate
(
NavigationDelegate
(
onPageStarted:
(
_
)
{
setState
(()
{
_isLoading
=
true
;
});
},
onPageFinished:
(
_
)
{
setState
(()
{
_isLoading
=
false
;
});
},
onNavigationRequest:
_handleNavigation
,
),
)
..
loadRequest
(
Uri
.
parse
(
input
.
url
));
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomAppBar
(
title:
"Thanh toán"
,
leftButtons:
[
CustomBackButton
(
onPressed:
()
async
{
if
(
input
.
showAlertBack
)
{
_onBackPressed
();
return
;
}
Get
.
back
();
},
),
],
),
body:
Stack
(
children:
[
WebViewWidget
(
controller:
_controller
),
if
(
_isLoading
)
const
Center
(
child:
CircularProgressIndicator
()),
],
),
);
}
NavigationDecision
_handleNavigation
(
NavigationRequest
request
)
{
final
url
=
request
.
url
;
debugPrint
(
"➡️ Navigating:
$url
"
);
if
(
paymentSuccessUrls
.
any
((
success
)
=>
url
.
startsWith
(
success
)))
{
_onPaymentResult
(
PaymentProcess
.
success
);
return
NavigationDecision
.
prevent
;
}
if
(
paymentFailedUrls
.
any
((
fail
)
=>
url
.
startsWith
(
fail
)))
{
_onPaymentResult
(
PaymentProcess
.
failure
);
return
NavigationDecision
.
prevent
;
}
// Mở app Zalo nếu redirect đến scheme của nó
final
uri
=
Uri
.
tryParse
(
url
);
final
zaloSchemes
=
[
"zalo"
,
"zalopay"
,
"zalopay.api.v2"
];
if
(
uri
!=
null
&&
zaloSchemes
.
contains
(
uri
.
scheme
))
{
launchUrl
(
uri
,
mode:
LaunchMode
.
externalApplication
);
return
NavigationDecision
.
prevent
;
}
return
NavigationDecision
.
navigate
;
}
void
_onPaymentResult
(
PaymentProcess
result
)
{
if
(
input
.
isContract
)
{
_navigateToContractScreen
();
}
else
if
(
input
.
callback
!=
null
)
{
input
.
callback
!(
result
);
Get
.
back
();
// hoặc điều hướng phù hợp
}
else
{
_backToRoot
();
}
}
void
_backToRoot
()
{
Get
.
until
((
route
)
=>
route
.
isFirst
);
}
void
_navigateToContractScreen
()
{
Get
.
snackbar
(
'Thông báo'
,
'Đi tới danh sách hợp đồng điện'
);
// placeholder
}
_onBackPressed
()
{
final
dataAlert
=
DataAlertModel
(
title:
"Huỷ đơn hàng?"
,
description:
"Bạn có chắc muốn huỷ thanh toán đơn hàng này?"
,
localHeaderImage:
"assets/images/ic_pipi_03.png"
,
buttons:
[
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
{
Get
.
offNamed
(
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
input
.
orderId
??
""
,
"canBack"
:
false
});
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Huỷ"
,
onPressed:
()
=>
Navigator
.
pop
(
context
,
false
),
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
,
),
],
);
showAlert
(
data:
dataAlert
);
}
}
lib/screen/webview/web_view_screen.dart
0 → 100644
View file @
33ec1dde
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
class
BaseWebViewInput
{
final
String
?
title
;
final
String
url
;
final
bool
isFullScreen
;
const
BaseWebViewInput
({
this
.
title
,
required
this
.
url
,
this
.
isFullScreen
=
false
,
});
}
class
BaseWebViewScreen
extends
StatefulWidget
{
const
BaseWebViewScreen
({
super
.
key
});
@override
State
<
BaseWebViewScreen
>
createState
()
=>
_BaseWebViewScreenState
();
}
class
_BaseWebViewScreenState
extends
State
<
BaseWebViewScreen
>
{
late
final
BaseWebViewInput
input
;
late
final
WebViewController
_controller
;
String
?
_dynamicTitle
;
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
BaseWebViewInput
)
{
input
=
args
;
}
else
{
throw
Exception
(
'BaseWebViewInput is required in arguments'
);
}
_controller
=
WebViewController
()
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
..
setNavigationDelegate
(
NavigationDelegate
(
onPageFinished:
(
_
)
async
{
final
title
=
await
_controller
.
getTitle
();
setState
(()
{
_dynamicTitle
=
title
;
});
},
onWebResourceError:
(
_
)
{
_showWebErrorDialog
();
},
),
)
..
loadRequest
(
Uri
.
parse
(
input
.
url
));
_clearCookies
();
}
Future
<
void
>
_clearCookies
()
async
{
final
cookieManager
=
WebViewCookieManager
();
await
cookieManager
.
clearCookies
();
}
void
_showWebErrorDialog
()
{
showDialog
(
context:
context
,
builder:
(
_
)
=>
AlertDialog
(
title:
const
Text
(
"Lỗi"
),
content:
const
Text
(
"Không thể tải nội dung. Vui lòng thử lại sau."
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
pop
(
context
),
child:
const
Text
(
"Đóng"
),
)
],
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
input
.
isFullScreen
?
null
:
AppBar
(
title:
Text
(
input
.
title
??
_dynamicTitle
??
Uri
.
parse
(
input
.
url
).
host
,
style:
const
TextStyle
(
fontSize:
16
),
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
_handleBack
,
),
),
body:
SafeArea
(
child:
WebViewWidget
(
controller:
_controller
),
),
);
}
void
_handleBack
()
async
{
if
(
await
_controller
.
canGoBack
())
{
_controller
.
goBack
();
}
else
{
if
(
context
.
mounted
)
Navigator
.
of
(
context
).
pop
();
}
}
}
\ No newline at end of file
lib/shared/router_gage.dart
View file @
33ec1dde
...
...
@@ -2,13 +2,18 @@ import 'package:get/get_navigation/src/routes/get_route.dart';
import
'../screen/game/game_cards/game_card_screen.dart'
;
import
'../screen/login/login_screen.dart'
;
import
'../screen/main_tab_screen/main_tab_screen.dart'
;
import
'../screen/notification/notification_screen.dart'
;
import
'../screen/onboarding/onboarding_screen.dart'
;
import
'../screen/register_campaign/register_form_input_screen.dart'
;
import
'../screen/setting/setting_screen.dart'
;
import
'../screen/splash/splash_screen.dart'
;
import
'../screen/support/support_screen.dart'
;
import
'../screen/transaction/history/transaction_history_detail_screen.dart'
;
import
'../screen/transaction/transaction_detail_screen.dart'
;
import
'../screen/voucher/detail/voucher_detail_screen.dart'
;
import
'../screen/voucher/voucher_list/voucher_list_screen.dart'
;
import
'../screen/webview/payment_web_view_screen.dart'
;
import
'../screen/webview/web_view_screen.dart'
;
const
splashScreen
=
'/splash'
;
const
onboardingScreen
=
'/onboarding'
;
...
...
@@ -20,6 +25,11 @@ const voucherDetailScreen = '/voucherDetail';
const
gameCardScreen
=
'/gameCardScreen'
;
const
registerFormInputScreen
=
'/registerFormInputScreen'
;
const
transactionDetailScreen
=
'/transactionDetailScreen'
;
const
baseWebViewScreen
=
'/baseWebViewScreen'
;
const
paymentWebViewScreen
=
'/paymentWebViewScreen'
;
const
transactionHistoryDetailScreen
=
'/transactionHistoryDetailScreen'
;
const
supportScreen
=
'/supportScreen'
;
const
notificationScreen
=
'/notificationScreen'
;
class
RouterPage
{
static
List
<
GetPage
>
pages
()
{
...
...
@@ -40,6 +50,11 @@ class RouterPage {
GetPage
(
name:
gameCardScreen
,
page:
()
=>
GameCardScreen
(),),
GetPage
(
name:
registerFormInputScreen
,
page:
()
=>
RegisterFormInputScreen
(),),
GetPage
(
name:
transactionDetailScreen
,
page:
()
=>
TransactionDetailScreen
(),),
GetPage
(
name:
baseWebViewScreen
,
page:
()
=>
BaseWebViewScreen
(),),
GetPage
(
name:
paymentWebViewScreen
,
page:
()
=>
PaymentWebViewScreen
(),),
GetPage
(
name:
transactionHistoryDetailScreen
,
page:
()
=>
TransactionHistoryDetailScreen
(),),
GetPage
(
name:
supportScreen
,
page:
()
=>
SupportScreen
(),),
GetPage
(
name:
notificationScreen
,
page:
()
=>
NotificationScreen
(),),
];
}
}
\ No newline at end of file
lib/widgets/alert/custom_alert_dialog.dart
View file @
33ec1dde
...
...
@@ -34,7 +34,7 @@ class CustomAlertDialog extends StatelessWidget {
_buildHeaderImage
(),
const
SizedBox
(
height:
2
),
// Title
if
(
alertData
.
title
!=
null
)
if
(
(
alertData
.
title
??
""
).
isNotEmpty
)
Text
(
alertData
.
title
!,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
),
...
...
@@ -48,7 +48,7 @@ class CustomAlertDialog extends StatelessWidget {
</div>
'''
),
const
SizedBox
(
height:
4
),
if
(
alertData
.
content
!=
null
)
if
(
(
alertData
.
content
??
""
).
isNotEmpty
)
Text
(
alertData
.
content
!,
style:
TextStyle
(
fontSize:
14
,
fontWeight:
FontWeight
.
w600
,
color:
BaseColor
.
primary500
),
...
...
pubspec.yaml
View file @
33ec1dde
...
...
@@ -48,6 +48,7 @@ dependencies:
local_auth
:
pin_code_fields
:
intl
:
^0.18.1
webview_flutter
:
^4.2.2
game_miniapp
:
path
:
../mini_app/game_miniapp
dev_dependencies
:
...
...
Prev
1
2
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