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
Expand all
Hide 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
This diff is collapsed.
Click to expand it.
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
)
{
...
...
@@ -61,4 +143,4 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
print
(
"Error fetching payment bank accounts:
$error
"
);
}
}
}
\ No newline at end of file
}
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,48 +392,49 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
return
_buildBottomActionContainer
(
child:
Row
(
children:
[
Row
(
children:
[
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
white
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
50
),
),
child:
IconButton
(
icon:
const
Icon
(
Icons
.
remove
,
color:
Colors
.
black
),
onPressed:
()
{
if
(
_viewModel
.
quantity
.
value
>
1
)
{
_viewModel
.
quantity
.
value
--;
}
},
),
),
const
SizedBox
(
width:
12
),
Obx
(()
=>
Text
(
'
${_viewModel.quantity.value}
'
,
style:
const
TextStyle
(
fontSize:
16
))),
const
SizedBox
(
width:
12
),
Container
(
decoration:
BoxDecoration
(
color:
BaseColor
.
primary500
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
50
),
if
(
_viewModel
.
product
.
value
?.
productType
==
ProductType
.
voucher
)
Row
(
children:
[
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
white
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
50
),
),
child:
IconButton
(
icon:
const
Icon
(
Icons
.
remove
,
color:
Colors
.
black
),
onPressed:
()
{
if
(
_viewModel
.
quantity
.
value
>
1
)
{
_viewModel
.
quantity
.
value
--;
}
},
),
),
child:
IconButton
(
icon:
const
Icon
(
Icons
.
add
,
color:
Colors
.
white
),
onPressed:
()
{
_viewModel
.
quantity
.
value
++;
},
const
SizedBox
(
width:
12
),
Obx
(()
=>
Text
(
'
${_viewModel.quantity.value}
'
,
style:
const
TextStyle
(
fontSize:
16
))),
const
SizedBox
(
width:
12
),
Container
(
decoration:
BoxDecoration
(
color:
BaseColor
.
primary500
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
50
),
),
child:
IconButton
(
icon:
const
Icon
(
Icons
.
add
,
color:
Colors
.
white
),
onPressed:
()
{
_viewModel
.
quantity
.
value
++;
},
),
),
),
],
),
],
),
const
SizedBox
(
width:
36
),
Expanded
(
child:
SizedBox
(
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,9 +83,29 @@ 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