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
417358c5
Commit
417358c5
authored
Aug 15, 2025
by
DatHV
Browse files
update authen 401, device manager, interestied category
parent
efb4662c
Changes
99
Show whitespace changes
Inline
Side-by-side
lib/screen/device_manager/device_info_popup.dart
0 → 100644
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'device_manager_model.dart'
;
class
DeviceInfoSheet
extends
StatelessWidget
{
final
DeviceItemModel
item
;
final
VoidCallback
?
onDelete
;
const
DeviceInfoSheet
({
super
.
key
,
required
this
.
item
,
this
.
onDelete
});
@override
Widget
build
(
BuildContext
context
)
{
return
SafeArea
(
top:
false
,
child:
Padding
(
padding:
const
EdgeInsets
.
fromLTRB
(
20
,
24
,
20
,
20
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
if
((
item
.
appName
??
''
).
isNotEmpty
)
Text
(
item
.
appName
??
''
,
textAlign:
TextAlign
.
center
,
style:
const
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
w700
),
),
if
(
item
.
isCurrent
==
true
)
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
6
),
decoration:
BoxDecoration
(
color:
Colors
.
green
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
24
)),
),
child:
Text
(
'Thiết bị hiện tại'
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w600
),
),
),
const
SizedBox
(
height:
32
),
_InfoRow
(
label:
'Đăng nhập:'
,
value:
item
.
lastLogin
??
'-'
),
const
SizedBox
(
height:
12
),
_InfoRow
(
label:
'Phương thức:'
,
value:
item
.
loginMethod2
??
'-'
),
const
SizedBox
(
height:
12
),
if
((
item
.
location
??
''
).
isNotEmpty
)
_InfoRow
(
label:
'Địa điểm:'
,
value:
item
.
location
??
'-'
),
if
((
item
.
location
??
''
).
isNotEmpty
)
SizedBox
(
height:
12
),
_InfoRow
(
label:
'Địa chỉ IP:'
,
value:
item
.
ipAddress
??
'-'
),
const
SizedBox
(
height:
32
),
if
(
item
.
isCurrent
!=
true
)
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
(
onPressed:
onDelete
,
style:
OutlinedButton
.
styleFrom
(
side:
BorderSide
(
color:
Colors
.
black26
),
padding:
const
EdgeInsets
.
symmetric
(
vertical:
16
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
)),
foregroundColor:
Colors
.
red
,
textStyle:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w600
),
),
child:
const
Text
(
'Xóa thiết bị'
),
),
),
],
),
),
);
}
}
class
_InfoRow
extends
StatelessWidget
{
final
String
label
;
final
String
value
;
const
_InfoRow
({
required
this
.
label
,
required
this
.
value
});
@override
Widget
build
(
BuildContext
context
)
{
final
styleLabel
=
TextStyle
(
color:
Colors
.
grey
.
shade700
,
fontSize:
16
,
fontWeight:
FontWeight
.
w600
);
const
styleValue
=
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w600
);
return
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
SizedBox
(
width:
110
,
// cố định để canh lề đẹp
child:
Text
(
label
,
style:
styleLabel
),
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
value
,
style:
styleValue
)),
],
);
}
}
lib/screen/device_manager/device_manager_model.dart
0 → 100644
View file @
417358c5
class
DeviceItemModel
{
String
?
deviceKey
;
String
?
lastLogin
;
String
?
model
;
String
?
version
;
String
?
appName
;
String
?
platform
;
String
?
ipAddress
;
String
?
loginMethod
;
String
?
loginMethod2
;
String
?
location
;
String
?
pastTime
;
String
?
statusDisplay
;
bool
?
isCurrent
;
DeviceItemModel
({
this
.
deviceKey
,
this
.
lastLogin
,
this
.
model
,
this
.
version
,
this
.
appName
,
this
.
platform
,
this
.
ipAddress
,
this
.
loginMethod
,
this
.
loginMethod2
,
this
.
location
,
this
.
pastTime
,
this
.
statusDisplay
,
this
.
isCurrent
,
});
factory
DeviceItemModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
DeviceItemModel
(
deviceKey:
json
[
'device_key'
]
as
String
?,
lastLogin:
json
[
'last_login'
]
as
String
?,
model:
json
[
'model'
]
as
String
?,
version:
json
[
'version'
]
as
String
?,
appName:
json
[
'app_name'
]
as
String
?,
platform:
json
[
'platform'
]
as
String
?,
ipAddress:
json
[
'ip_address'
]
as
String
?,
loginMethod:
json
[
'login_method'
]
as
String
?,
loginMethod2:
json
[
'login_method2'
]
as
String
?,
location:
json
[
'location'
]
as
String
?,
pastTime:
json
[
'past_time'
]
as
String
?,
statusDisplay:
json
[
'status_display'
]
as
String
?,
isCurrent:
json
[
'is_current'
]
as
bool
?,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'device_key'
:
deviceKey
,
'last_login'
:
lastLogin
,
'model'
:
model
,
'version'
:
version
,
'app_name'
:
appName
,
'platform'
:
platform
,
'ip_address'
:
ipAddress
,
'login_method'
:
loginMethod
,
'login_method2'
:
loginMethod2
,
'location'
:
location
,
'past_time'
:
pastTime
,
'status_display'
:
statusDisplay
,
'is_current'
:
isCurrent
,
};
}
}
class
DevicesLogoutListResponse
{
List
<
DeviceItemModel
>?
devices
;
int
?
total
;
DevicesLogoutListResponse
({
this
.
devices
,
this
.
total
});
factory
DevicesLogoutListResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
DevicesLogoutListResponse
(
devices:
(
json
[
'devices'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
DeviceItemModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
total:
json
[
'total'
]
as
int
?,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'devices'
:
devices
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
'total'
:
total
,
};
}
}
lib/screen/device_manager/device_manager_screen.dart
0 → 100644
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'device_info_popup.dart'
;
import
'device_manager_model.dart'
;
import
'device_manager_viewmodel.dart'
;
import
'logged_out_devices_screen.dart'
;
class
DeviceManagerScreen
extends
BaseScreen
{
const
DeviceManagerScreen
({
super
.
key
});
@override
State
<
DeviceManagerScreen
>
createState
()
=>
_DeviceManagerScreenState
();
}
class
_DeviceManagerScreenState
extends
BaseState
<
DeviceManagerScreen
>
with
BasicState
{
final
_viewModel
=
DeviceManagerViewModel
();
@override
void
initState
()
{
super
.
initState
();
_viewModel
.
getData
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Quản lý thiết bị đăng nhập'
),
body:
Obx
(()
{
final
logoutDevices
=
_viewModel
.
logoutDevicesResponse
.
value
?.
devices
??
[];
final
logoutDisplayDevices
=
logoutDevices
.
take
(
3
).
toList
()
??
[];
final
currentDevice
=
_viewModel
.
currentDevice
.
value
;
return
RefreshIndicator
(
onRefresh:
()
async
=>
_refresh
(),
child:
ListView
(
padding:
EdgeInsets
.
zero
,
children:
[
const
_SectionHeader
(
'Thiết bị hiện tại'
),
if
(
currentDevice
==
null
)
const
_EmptyRow
(
'Không có thiết bị hiện tại'
)
else
DeviceItemWidget
(
item:
currentDevice
,
onMore:
()
=>
_showMore
(
currentDevice
)),
const
Divider
(
height:
12
,
thickness:
8
),
const
_SectionHeader
(
'Thiết bị đã đăng xuất gần đây'
),
const
SizedBox
(
height:
12
),
if
(
logoutDisplayDevices
.
isEmpty
)
EmptyWidget
(
content:
'Chưa có thiết bị đã đăng xuất gần đây'
)
else
...
logoutDisplayDevices
.
map
((
e
)
=>
DeviceItemWidget
(
item:
e
,
onMore:
()
=>
_showMore
(
e
))),
const
SizedBox
(
height:
16
),
if
(
logoutDisplayDevices
.
isNotEmpty
)
Center
(
child:
TextButton
(
onPressed:
()
{
Get
.
to
(()
=>
LoggedOutDeviceScreen
());
},
child:
Text
(
'Xem tất cả thiết bị đã đăng xuất (
${logoutDevices.length}
)'
,
style:
const
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
w700
,
color:
Colors
.
blueAccent
),
),
),
),
const
SizedBox
(
height:
24
),
],
),
);
}),
);
}
void
_refresh
()
{
_viewModel
.
getData
();
}
void
_showMore
(
DeviceItemModel
item
)
async
{
BottomSheetHelper
.
showBottomSheetPopup
(
child:
DeviceInfoSheet
(
item:
item
,
onDelete:
()
{
Get
.
back
();
_viewModel
.
deleteDevice
(
item
);
},
),
backgroundContainerColor:
Colors
.
white
,
);
}
}
class
_SectionHeader
extends
StatelessWidget
{
final
String
text
;
const
_SectionHeader
(
this
.
text
);
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
color:
Colors
.
grey
.
shade100
,
padding:
const
EdgeInsets
.
fromLTRB
(
16
,
16
,
16
,
8
),
child:
Text
(
text
,
style:
const
TextStyle
(
fontSize:
19
,
fontWeight:
FontWeight
.
w800
)),
);
}
}
class
_EmptyRow
extends
StatelessWidget
{
final
String
text
;
const
_EmptyRow
(
this
.
text
);
@override
Widget
build
(
BuildContext
context
)
{
return
ListTile
(
leading:
const
Icon
(
Icons
.
devices_other
,
color:
Colors
.
grey
),
title:
Text
(
text
,
style:
const
TextStyle
(
color:
Colors
.
grey
)),
);
}
}
class
DeviceItemWidget
extends
StatelessWidget
{
final
DeviceItemModel
item
;
final
VoidCallback
onMore
;
const
DeviceItemWidget
({
super
.
key
,
required
this
.
item
,
required
this
.
onMore
});
@override
Widget
build
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
onMore
,
child:
Column
(
children:
[
ListTile
(
leading:
_DeviceIcon
(
platform:
item
.
platform
),
title:
(
item
.
appName
??
''
).
isNotEmpty
?
Text
(
item
.
appName
??
''
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
w600
,
color:
Colors
.
black87
),
)
:
null
,
subtitle:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
if
((
item
.
loginMethod
??
''
).
isNotEmpty
)
Padding
(
padding:
const
EdgeInsets
.
only
(
top:
2.0
,
bottom:
2.0
),
child:
Text
(
item
.
loginMethod
!,
style:
TextStyle
(
color:
Colors
.
grey
.
shade600
)),
),
if
((
item
.
lastLogin
??
''
).
isNotEmpty
)
Text
(
item
.
lastLogin
!,
style:
TextStyle
(
color:
Colors
.
grey
.
shade600
)),
],
),
trailing:
IconButton
(
icon:
const
Icon
(
Icons
.
more_horiz
),
onPressed:
onMore
),
contentPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
6
),
),
Divider
(
height:
1
,
color:
Colors
.
grey
.
shade200
),
],
),
);
}
}
class
_DeviceIcon
extends
StatelessWidget
{
final
String
?
platform
;
const
_DeviceIcon
({
this
.
platform
});
@override
Widget
build
(
BuildContext
context
)
{
final
isIOS
=
(
platform
??
''
).
toLowerCase
().
contains
(
'ios'
);
final
isAndroid
=
(
platform
??
''
).
toLowerCase
().
contains
(
'android'
);
final
icon
=
isIOS
?
Icons
.
phone_iphone_rounded
:
isAndroid
?
Icons
.
android_rounded
:
Icons
.
devices_other_rounded
;
return
Icon
(
icon
,
size:
36
,
color:
Colors
.
grey
.
shade700
);
}
}
lib/screen/device_manager/device_manager_viewmodel.dart
0 → 100644
View file @
417358c5
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
'../../base/restful_api_viewmodel.dart'
;
import
'device_manager_model.dart'
;
class
DeviceManagerViewModel
extends
RestfulApiViewModel
{
var
logoutDevicesResponse
=
Rxn
<
DevicesLogoutListResponse
>();
var
currentDevice
=
Rxn
<
DeviceItemModel
>();
void
Function
(
String
message
)?
onShowAlertError
;
getData
()
{
getLogoutDevicesResponse
();
getCurrentDevice
();
}
Future
<
void
>
getLogoutDevicesResponse
()
async
{
final
body
=
{
"page"
:
0
,
"limit"
:
200
};
showLoading
();
try
{
final
response
=
await
client
.
getLogoutDevices
(
body
);
hideLoading
();
if
(
response
.
isSuccess
&&
response
.
data
!=
null
)
{
logoutDevicesResponse
.
value
=
response
.
data
;
}
else
{
onShowAlertError
?.
call
(
response
.
message
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
Future
<
void
>
getCurrentDevice
()
async
{
final
response
=
await
client
.
getCurrentDevice
();
if
(
response
.
isSuccess
&&
response
.
data
!=
null
)
{
currentDevice
.
value
=
response
.
data
;
}
}
Future
<
void
>
deleteDevice
(
DeviceItemModel
item
)
async
{
if
((
item
.
deviceKey
??
''
).
isEmpty
)
return
;
showLoading
();
try
{
final
response
=
await
client
.
deleteDevice
(
item
.
deviceKey
??
''
);
hideLoading
();
if
(
response
.
isSuccess
)
{
getLogoutDevicesResponse
();
onShowAlertError
?.
call
(
response
.
data
??
"Đã xóa thiết bị thành công"
);
}
else
{
onShowAlertError
?.
call
(
response
.
message
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
}
lib/screen/device_manager/logged_out_devices_screen.dart
0 → 100644
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'device_info_popup.dart'
;
import
'device_manager_model.dart'
;
import
'device_manager_screen.dart'
;
import
'device_manager_viewmodel.dart'
;
class
LoggedOutDeviceScreen
extends
BaseScreen
{
const
LoggedOutDeviceScreen
({
super
.
key
});
@override
State
<
LoggedOutDeviceScreen
>
createState
()
=>
_LoggedOutDeviceScreenState
();
}
class
_LoggedOutDeviceScreenState
extends
BaseState
<
LoggedOutDeviceScreen
>
with
BasicState
{
final
_viewModel
=
DeviceManagerViewModel
();
@override
void
initState
()
{
super
.
initState
();
_viewModel
.
getLogoutDevicesResponse
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Các thiết bị đã đăng xuất'
),
body:
Obx
(()
{
final
logoutDevices
=
_viewModel
.
logoutDevicesResponse
.
value
?.
devices
??
[];
return
RefreshIndicator
(
onRefresh:
()
async
=>
_refresh
(),
child:
ListView
(
padding:
EdgeInsets
.
zero
,
children:
[
const
SizedBox
(
height:
12
),
if
(
logoutDevices
.
isEmpty
)
EmptyWidget
(
content:
'Chưa có thiết bị đã đăng xuất gần đây'
)
else
...
logoutDevices
.
map
((
e
)
=>
DeviceItemWidget
(
item:
e
,
onMore:
()
=>
_showMore
(
e
))),
const
SizedBox
(
height:
32
),
],
),
);
}),
);
}
void
_refresh
()
{
_viewModel
.
getLogoutDevicesResponse
();
}
void
_showMore
(
DeviceItemModel
item
)
async
{
BottomSheetHelper
.
showBottomSheetPopup
(
child:
DeviceInfoSheet
(
item:
item
,
onDelete:
()
{
Get
.
back
();
_viewModel
.
deleteDevice
(
item
);
}),
backgroundContainerColor:
Colors
.
white
,
);
}
}
lib/screen/electric_payment/electric_payment_bill_screen.dart
0 → 100644
View file @
417358c5
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/widgets/image_loader.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../transaction/model/payment_method_model.dart'
;
import
'../webview/payment_web_view_screen.dart'
;
import
'models/customer_contract_object_model.dart'
;
import
'electric_payment_bill_viewmodel.dart'
;
class
ElectricPaymentBillScreen
extends
StatefulWidget
{
final
CustomerContractModel
bill
;
const
ElectricPaymentBillScreen
({
super
.
key
,
required
this
.
bill
});
@override
State
<
ElectricPaymentBillScreen
>
createState
()
=>
_ElectricPaymentBillScreenState
();
}
class
_ElectricPaymentBillScreenState
extends
State
<
ElectricPaymentBillScreen
>
{
final
_viewModel
=
ElectricPaymentBillViewModel
();
@override
void
initState
()
{
super
.
initState
();
_viewModel
.
customerEvnPaymentGatewayResponse
=
(
data
)
{
Get
.
toNamed
(
paymentWebViewScreen
,
arguments:
PaymentWebViewInput
(
url:
data
?.
vitapayData
??
""
,
isContract:
false
,
orderId:
data
?.
requestId
??
""
,
showAlertBack:
false
,
callback:
(
result
)
{
if
(
result
==
PaymentProcess
.
success
)
{
Get
.
offNamed
(
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
data
?.
requestId
??
""
,
"canBack"
:
true
},
);
}
},
)
);
};
_viewModel
.
getPaymentMethods
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Chi tiết hóa đơn điện'
),
body:
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
return
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Row
(
children:
[
Image
.
asset
(
'assets/images/ic_evn_logo.png'
,
height:
48
,
fit:
BoxFit
.
cover
),
const
SizedBox
(
width:
12
),
Text
(
'Điện lực
${widget.bill.location ?? ''}
'
,
style:
TextStyle
(
fontSize:
18
,
color:
Colors
.
blueAccent
,
fontWeight:
FontWeight
.
w700
),
),
],
),
),
Divider
(
thickness:
1
,
color:
Colors
.
grey
[
200
]),
const
SizedBox
(
height:
4
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildInfoRow
(
'Mã khách hàng:'
,
widget
.
bill
.
maKH
),
_buildInfoRow
(
'Tên khách hàng:'
,
widget
.
bill
.
nameKH
),
_buildInfoRow
(
'Mã hóa đơn:'
,
widget
.
bill
.
idHoaHon
),
_buildInfoRow
(
'Kỳ hóa đơn:'
,
widget
.
bill
.
ky
),
_buildInfoRow
(
'Tổng giá trị hóa đơn:'
,
(
widget
.
bill
.
amount
??
0
).
money
(
CurrencyUnit
.
VND
),
valueColor:
Colors
.
orange
,
),
],
),
),
const
SizedBox
(
height:
16
),
Divider
(
thickness:
1
,
color:
Colors
.
grey
[
200
]),
const
SizedBox
(
height:
16
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
Text
(
'Phương thức thanh toán'
,
style:
TextStyle
(
fontSize:
18
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w600
),
),
),
const
SizedBox
(
height:
12
),
_buildPaymentMethods
(),
],
),
);
},
),
bottomNavigationBar:
_buildBottomButton
(),
);
}
Widget
_buildBottomButton
()
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
16
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black54
,
blurRadius:
8
,
offset:
Offset
(
0
,
4
))],
),
child:
SafeArea
(
top:
false
,
child:
ElevatedButton
(
onPressed:
()
{
_viewModel
.
customerEvnPaymentGatewayRequest
(
widget
.
bill
);
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
minimumSize:
const
Size
.
fromHeight
(
52
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
)),
),
child:
Text
(
'Thanh toán'
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w700
)),
),
),
);
}
Widget
_buildInfoRow
(
String
label
,
String
?
value
,
{
Color
?
valueColor
})
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
6
),
child:
Row
(
children:
[
SizedBox
(
width:
180
,
child:
Text
(
label
,
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w500
,
color:
Colors
.
black54
),
),
),
Text
(
textAlign:
TextAlign
.
start
,
value
??
''
,
style:
TextStyle
(
fontSize:
17
,
color:
valueColor
??
Colors
.
black87
,
fontWeight:
valueColor
!=
null
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
),
],
),
);
}
Widget
_buildPaymentMethods
()
{
final
methods
=
_viewModel
.
paymentMethods
;
return
Obx
(
()
=>
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
color:
Colors
.
white
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
,
width:
1
),
),
child:
Column
(
children:
List
.
generate
(
methods
.
length
,
(
index
)
=>
_buildPaymentMethodItem
(
methods
[
index
],
index
)),
),
),
);
}
Widget
_buildPaymentMethodItem
(
PaymentMethodModel
method
,
int
index
)
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
RadioListTile
<
int
>(
activeColor:
BaseColor
.
primary400
,
value:
index
,
groupValue:
_viewModel
.
selectedPaymentMethodIndex
.
value
,
onChanged:
(
val
)
{
setState
(()
{
_viewModel
.
selectedPaymentMethodIndex
.
value
=
val
??
0
;
});
},
contentPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
title:
Row
(
children:
[
if
(
method
.
logo
!=
null
)
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
8
),
child:
loadNetworkImage
(
url:
method
.
logo
,
width:
24
,
height:
24
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/ic_logo.png'
,
),
),
Text
(
method
.
name
??
''
,
style:
const
TextStyle
(
fontSize:
16
)),
],
),
),
if
(
index
<
_viewModel
.
paymentMethods
.
length
-
1
)
Container
(
height:
1
,
color:
Colors
.
grey
.
shade200
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
)),
],
);
}
}
lib/screen/electric_payment/electric_payment_bill_viewmodel.dart
0 → 100644
View file @
417358c5
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/extensions/collection_extension.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../configs/constants.dart'
;
import
'../transaction/model/payment_method_model.dart'
;
import
'models/customer_contract_object_model.dart'
;
import
'models/electric_payment_response_model.dart'
;
class
ElectricPaymentBillViewModel
extends
RestfulApiViewModel
{
var
paymentMethods
=
RxList
<
PaymentMethodModel
>();
var
selectedPaymentMethodIndex
=
0
.
obs
;
void
Function
(
ElectricPaymentResponseModel
data
)?
customerEvnPaymentGatewayResponse
;
void
Function
(
String
message
)?
onShowAlertError
;
Future
<
void
>
getPaymentMethods
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
getPreviewPaymentMethods
();
hideLoading
();
selectedPaymentMethodIndex
.
value
=
0
;
paymentMethods
.
value
=
response
.
data
??
[];
}
catch
(
error
)
{
hideLoading
();
}
}
customerEvnPaymentGatewayRequest
(
CustomerContractModel
bill
)
async
{
final
paymentMethod
=
paymentMethods
.
value
.
safe
(
selectedPaymentMethodIndex
.
value
??
0
)
??
paymentMethods
.
firstOrNull
;
final
paymentMethodType
=
paymentMethod
?.
type
?.
methodBillEVN
??
''
;
if
(
paymentMethodType
.
isEmpty
)
{
onShowAlertError
?.
call
(
"Vui lòng chọn phương thức thanh toán."
);
return
;
}
showLoading
();
try
{
final
response
=
await
client
.
customerEvnPaymentGatewayRequest
(
bill
,
paymentMethodType
);
hideLoading
();
if
(
response
.
isSuccess
&&
response
.
data
!=
null
)
{
customerEvnPaymentGatewayResponse
?.
call
(
response
.
data
!);
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
"Lỗi khi thanh toán, vui lòng thử lại sau."
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
}
lib/screen/electric_payment/electric_payment_history_screen.dart
View file @
417358c5
...
...
@@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'customer_contract_object_model.dart'
;
import
'models/customer_contract_object_model.dart'
;
import
'electric_payment_bill_screen.dart'
;
import
'electric_payment_viewmodel.dart'
;
class
ElectricPaymentHistoryScreen
extends
StatefulWidget
{
...
...
@@ -121,42 +122,98 @@ class _ElectricPaymentHistoryScreenState extends State<ElectricPaymentHistoryScr
final
bill
=
_viewModel
.
billContracts
.
value
[
index
];
final
isSelected
=
selectedCodes
.
contains
(
bill
.
maKH
??
''
);
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
8
),
child:
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
ListTile
(
leading:
isEditMode
?
Checkbox
(
value:
isSelected
,
onChanged:
(
val
)
=>
toggleItem
(
bill
,
val
))
:
null
,
title:
Text
(
bill
.
location
??
''
),
subtitle:
Text
(
bill
.
maKH
??
''
),
trailing:
((
bill
.
amount
??
0
)
==
0
)
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
if
(
isEditMode
)
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
8
),
child:
Checkbox
(
value:
isSelected
,
onChanged:
(
val
)
=>
toggleItem
(
bill
,
val
)),
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
bill
.
location
??
''
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
)),
const
SizedBox
(
height:
6
),
if
((
bill
.
maKH
??
''
).
isNotEmpty
)
Text
(
bill
.
maKH
??
''
),
const
SizedBox
(
height:
6
),
if
((
bill
.
nameKH
??
''
).
isNotEmpty
)
Text
(
bill
.
nameKH
??
''
),
],
),
),
const
SizedBox
(
width:
12
),
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
100
,
maxWidth:
140
),
child:
(
bill
.
amount
??
0
)
==
0
?
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
const
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
),
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
const
[
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
),
SizedBox
(
height:
4
),
Text
(
'Bạn đã hết nợ cước'
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
green
),
style:
TextStyle
(
fontSize:
12
,
color:
Colors
.
green
),
),
],
)
:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
const
Icon
(
Icons
.
error
,
color:
Colors
.
red
),
Text
(
(
bill
.
amount
??
0
).
money
(
CurrencyUnit
.
vnd
),
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
red
),
(
bill
.
amount
??
0
).
money
(
CurrencyUnit
.
VND
),
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
bold
,
),
),
const
SizedBox
(
height:
8
),
GestureDetector
(
onTap:
()
{
Get
.
to
(
ElectricPaymentBillScreen
(
bill:
bill
,));
print
(
'Thanh toán hoá đơn:
${bill.maKH ?? ''}
'
);
},
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
8
),
decoration:
BoxDecoration
(
color:
BaseColor
.
primary500
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
const
Text
(
'Thanh toán'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
],
),
),
],
),
),
),
),
);
},
...
...
lib/screen/electric_payment/electric_payment_screen.dart
View file @
417358c5
...
...
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'electric_payment_viewmodel.dart'
;
...
...
lib/screen/electric_payment/electric_payment_viewmodel.dart
View file @
417358c5
import
'package:get/get
_rx/src/rx_types/rx_types
.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../configs/constants.dart'
;
import
'customer_contract_object_model.dart'
;
import
'electric_payment_bill_screen.dart'
;
import
'models/customer_contract_object_model.dart'
;
class
ElectricPaymentViewModel
extends
RestfulApiViewModel
{
var
responseData
=
Rxn
<
CustomerContractModel
>();
void
Function
(
String
message
)?
onShowAlertError
;
final
RxList
<
CustomerContractModel
>
billContracts
=
<
CustomerContractModel
>[].
obs
;
...
...
@@ -22,8 +22,7 @@ class ElectricPaymentViewModel extends RestfulApiViewModel {
if
((
result
.
amount
??
0
)
==
0
)
{
onShowAlertError
?.
call
(
"Bạn đã thanh toán hết hóa đơn."
);
}
else
{
// TODO
responseData
.
value
=
result
;
Get
.
to
(
ElectricPaymentBillScreen
(
bill:
result
,));
}
}
else
{
onShowAlertError
?.
call
(
"Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại."
);
...
...
lib/screen/electric_payment/customer_contract_object_model.dart
→
lib/screen/electric_payment/
models/
customer_contract_object_model.dart
View file @
417358c5
File moved
lib/screen/electric_payment/models/electric_payment_response_model.dart
0 → 100644
View file @
417358c5
import
'package:json_annotation/json_annotation.dart'
;
part
'electric_payment_response_model.g.dart'
;
@JsonSerializable
()
class
ElectricPaymentResponseModel
{
@JsonKey
(
name:
'request_id'
)
final
String
?
requestId
;
@JsonKey
(
name:
'vitapay_status'
)
final
String
?
vitapayStatus
;
@JsonKey
(
name:
'vitapay_message'
)
final
String
?
vitapayMessage
;
@JsonKey
(
name:
'vitapay_amount'
)
final
double
?
vitapayAmount
;
@JsonKey
(
name:
'vitapay_datatype'
)
final
String
?
vitapayDatatype
;
@JsonKey
(
name:
'vitapay_data'
)
final
String
?
vitapayData
;
@JsonKey
(
name:
'item_ids'
)
final
String
?
itemIds
;
ElectricPaymentResponseModel
({
this
.
requestId
,
this
.
vitapayStatus
,
this
.
vitapayMessage
,
this
.
vitapayAmount
,
this
.
vitapayDatatype
,
this
.
vitapayData
,
this
.
itemIds
,
});
factory
ElectricPaymentResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ElectricPaymentResponseModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$ElectricPaymentResponseModelToJson
(
this
);
}
lib/screen/electric_payment/models/electric_payment_response_model.g.dart
0 → 100644
View file @
417358c5
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'electric_payment_response_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ElectricPaymentResponseModel
_$ElectricPaymentResponseModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ElectricPaymentResponseModel
(
requestId:
json
[
'request_id'
]
as
String
?,
vitapayStatus:
json
[
'vitapay_status'
]
as
String
?,
vitapayMessage:
json
[
'vitapay_message'
]
as
String
?,
vitapayAmount:
(
json
[
'vitapay_amount'
]
as
num
?)?.
toDouble
(),
vitapayDatatype:
json
[
'vitapay_datatype'
]
as
String
?,
vitapayData:
json
[
'vitapay_data'
]
as
String
?,
itemIds:
json
[
'item_ids'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$ElectricPaymentResponseModelToJson
(
ElectricPaymentResponseModel
instance
,
)
=>
<
String
,
dynamic
>{
'request_id'
:
instance
.
requestId
,
'vitapay_status'
:
instance
.
vitapayStatus
,
'vitapay_message'
:
instance
.
vitapayMessage
,
'vitapay_amount'
:
instance
.
vitapayAmount
,
'vitapay_datatype'
:
instance
.
vitapayDatatype
,
'vitapay_data'
:
instance
.
vitapayData
,
'item_ids'
:
instance
.
itemIds
,
};
lib/screen/faqs/faqs_screen.dart
View file @
417358c5
...
...
@@ -5,7 +5,7 @@ import 'package:mypoint_flutter_app/screen/pageDetail/campaign_detail_screen.dar
import
'package:mypoint_flutter_app/widgets/back_button.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'faqs_viewmodel.dart'
;
...
...
lib/screen/home/home_screen.dart
View file @
417358c5
...
...
@@ -7,6 +7,8 @@ import 'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widge
import
'package:mypoint_flutter_app/screen/home/pipi_detail_screen.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'../../preference/point/header_home_model.dart'
;
import
'../popup_manager/popup_manager_model.dart'
;
import
'../popup_manager/popup_manager_popup.dart'
;
import
'../voucher/sub_widget/voucher_section_title.dart'
;
import
'custom_widget/achievement_carousel_widget.dart'
;
import
'custom_widget/affiliate_brand_grid_widget.dart'
;
...
...
@@ -223,15 +225,44 @@ class _HomeScreenState extends State<HomeScreen> {
}
void
_handleHoverViewTap
()
{
final
result
=
_viewModel
.
hoverData
.
value
?.
direction
?.
begin
();
if
(
result
!=
true
)
{
showModalBottomSheet
(
context:
context
,
backgroundColor:
Colors
.
transparent
,
isScrollControlled:
true
,
builder:
(
_
)
=>
PipiDetailScreen
(),
showPopup
(
context
,
modelPopup:
PopupManagerModel
(
timeCountDown:
'10'
,
id:
'popup123'
,
requestId:
'req_abc'
,
popupTitleTemplate:
'Khuyến mãi đặc biệt'
,
popupBodyTemplate:
'Giảm 50% cho đơn hàng hôm nay.'
,
imageURL:
'https://picsum.photos/1200/800'
,
clickActionType:
'VIEW_GIFT'
,
clickActionParam:
'gift_999'
,
),
onNavigate:
({
required
String
name
,
required
String
identifier
,
String
?
title
,
String
?
body
,
})
{
// Tự nối qua router của bạn
// ví dụ:
// context.pushNamed(name, extra: {'id': identifier, 'title': title, 'body': body});
debugPrint
(
'Navigate ->
$name
, id=
$identifier
'
);
},
onDismissed:
()
{
// Thay cho NotificationCenter.dismissPopup
debugPrint
(
'Popup dismissed'
);
},
);
}
// final result = _viewModel.hoverData.value?.direction?.begin();
// if (result != true) {
// showModalBottomSheet(
// context: context,
// backgroundColor: Colors.transparent,
// isScrollControlled: true,
// builder: (_) => PipiDetailScreen(),
// );
// }
}
void
_handleCloseHoverView
()
{
...
...
@@ -241,7 +272,6 @@ class _HomeScreenState extends State<HomeScreen> {
}
Future
<
void
>
_onRefresh
()
async
{
print
(
"onRefresh"
);
await
_viewModel
.
getSectionLayoutHome
();
await
_viewModel
.
loadDataPiPiHome
();
await
_headerHomeVM
.
freshData
();
...
...
@@ -250,41 +280,4 @@ class _HomeScreenState extends State<HomeScreen> {
void
_showMiniGame
(
BuildContext
context
)
async
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
_
)
=>
const
GameMiniAppScreen
()));
}
void
_logout
(
BuildContext
context
)
async
{
final
confirm
=
await
showDialog
<
bool
>(
context:
context
,
builder:
(
ctx
)
=>
AlertDialog
(
title:
const
Text
(
'Xác nhận'
),
content:
const
Text
(
'Bạn có chắc muốn đăng xuất?'
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
ctx
).
pop
(
false
),
child:
const
Text
(
'Hủy'
)),
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
ctx
).
pop
(
true
),
child:
const
Text
(
'Đăng xuất'
)),
],
),
);
if
(
confirm
==
true
)
{
DataPreference
.
instance
.
clearLoginToken
();
_safeBackToLogin
();
}
}
void
_safeBackToLogin
()
{
bool
found
=
false
;
Navigator
.
popUntil
(
Get
.
context
!,
(
route
)
{
final
matched
=
route
.
settings
.
name
==
loginScreen
;
if
(
matched
)
found
=
true
;
return
matched
;
});
final
phone
=
DataPreference
.
instance
.
phone
;
if
(
phone
!=
null
)
{
if
(!
found
)
{
Get
.
offAllNamed
(
loginScreen
,
arguments:
phone
);
}
}
else
{
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
}
}
}
lib/screen/interested_categories/interestied_categories_screen.dart
0 → 100644
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'interestied_categories_viewmodel.dart'
;
class
InterestCategoriesScreen
extends
BaseScreen
{
const
InterestCategoriesScreen
({
super
.
key
});
@override
State
<
InterestCategoriesScreen
>
createState
()
=>
_InterestCategoriesScreenState
();
}
class
_InterestCategoriesScreenState
extends
BaseState
<
InterestCategoriesScreen
>
with
BasicState
{
final
_viewModel
=
InterestedCategoriesViewModel
();
bool
_onChange
=
false
;
@override
void
initState
()
{
super
.
initState
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
,
headerImage:
"assets/images/ic_pipi_05.png"
);
}
};
_viewModel
.
getInterestedCategories
();
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Các lĩnh vực quan tâm'
),
body:
Obx
(()
{
final
listItems
=
_viewModel
.
interestedCategories
.
value
?.
listItems
??
[];
return
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
const
SizedBox
(
height:
8
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
const
Align
(
alignment:
Alignment
.
centerLeft
,
child:
Text
(
'Bạn quan tâm lĩnh vực nào? Cho MyPoint biết để được gợi ý các ưu đãi phù hợp nhé!'
,
style:
TextStyle
(
fontSize:
17
,
height:
1.35
,
color:
Colors
.
black87
),
),
),
),
const
SizedBox
(
height:
16
),
Flexible
(
child:
GridView
.
builder
(
padding:
const
EdgeInsets
.
only
(
bottom:
16
,
left:
16
,
right:
16
),
itemCount:
listItems
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
crossAxisSpacing:
16
,
mainAxisSpacing:
16
,
childAspectRatio:
1.6
,
),
itemBuilder:
(
context
,
index
)
{
final
it
=
listItems
[
index
];
final
id
=
it
.
categoryCode
??
'
$index
'
;
final
selected
=
_viewModel
.
selectedIds
.
contains
(
id
);
return
_InterestCard
(
title:
it
.
categoryName
??
''
,
imageUrl:
it
.
imageUrl
,
selected:
selected
,
onTap:
()
{
setState
(()
{
_onChange
=
true
;
if
(
selected
)
{
_viewModel
.
selectedIds
.
remove
(
id
);
}
else
{
_viewModel
.
selectedIds
.
add
(
id
);
}
});
},
);
},
),
),
],
);
}),
bottomNavigationBar:
_buildBottomButton
(),
);
}
Widget
_buildBottomButton
()
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
16
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black54
,
blurRadius:
8
,
offset:
Offset
(
0
,
4
))],
),
child:
SafeArea
(
top:
false
,
child:
ElevatedButton
(
onPressed:
_onChange
?
()
{
_viewModel
.
submitInterestedCategories
();
}
:
null
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
_onChange
?
BaseColor
.
primary500
:
Colors
.
grey
,
minimumSize:
const
Size
.
fromHeight
(
52
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
)),
),
child:
Text
(
'Cập nhật'
,
style:
TextStyle
(
fontSize:
16
,
color:
_onChange
?
Colors
.
white
:
Colors
.
black87
,
fontWeight:
FontWeight
.
w700
,
),
),
),
),
);
}
}
class
_InterestCard
extends
StatelessWidget
{
final
String
title
;
final
String
?
imageUrl
;
final
bool
selected
;
final
VoidCallback
onTap
;
const
_InterestCard
({
required
this
.
title
,
required
this
.
imageUrl
,
required
this
.
selected
,
required
this
.
onTap
});
@override
Widget
build
(
BuildContext
context
)
{
final
Color
base
=
selected
?
const
Color
(
0xFFFF5A67
)
:
const
Color
(
0xFFFF7B85
);
final
Color
light
=
selected
?
const
Color
(
0xFFFF9CA3
)
:
const
Color
(
0xFFFFC2C6
);
return
InkWell
(
borderRadius:
BorderRadius
.
circular
(
20
),
onTap:
onTap
,
child:
Ink
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
20
),
gradient:
LinearGradient
(
begin:
Alignment
.
centerLeft
,
end:
Alignment
.
centerRight
,
colors:
[
base
,
light
]),
boxShadow:
selected
?
[
BoxShadow
(
color:
base
.
withOpacity
(
0.35
),
blurRadius:
12
,
offset:
const
Offset
(
0
,
6
))]
:
null
,
),
child:
Stack
(
children:
[
Image
.
asset
(
'assets/images/bg_item_category.png'
,
fit:
BoxFit
.
contain
),
Positioned
(
bottom:
10
,
left:
12
,
child:
Text
(
title
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
18
,
fontWeight:
FontWeight
.
w700
),
),
),
Positioned
(
top:
10
,
right:
12
,
child:
Container
(
width:
52
,
height:
52
,
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
)),
clipBehavior:
Clip
.
antiAlias
,
child:
imageUrl
?.
isNotEmpty
==
true
?
Image
.
network
(
imageUrl
!,
fit:
BoxFit
.
cover
)
:
const
Icon
(
Icons
.
category
,
color:
Colors
.
black54
),
),
),
// tick chọn
Positioned
(
top:
10
,
left:
12
,
child:
AnimatedScale
(
duration:
const
Duration
(
milliseconds:
150
),
scale:
selected
?
1
:
0
,
child:
Container
(
padding:
const
EdgeInsets
.
all
(
0.5
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
shape:
BoxShape
.
circle
),
child:
const
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
,
size:
28
),
),
),
),
],
),
),
);
}
}
lib/screen/interested_categories/interestied_categories_viewmodel.dart
0 → 100644
View file @
417358c5
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../configs/constants.dart'
;
import
'models/interested_categories_model.dart'
;
class
InterestedCategoriesViewModel
extends
RestfulApiViewModel
{
var
interestedCategories
=
Rxn
<
InterestedCategoriesResponse
>();
Set
<
String
>
selectedIds
=
{};
void
Function
(
String
message
)?
onShowAlertError
;
Future
<
void
>
getInterestedCategories
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
categoryTopLevelGetList
();
hideLoading
();
if
(
response
.
isSuccess
&&
response
.
data
!=
null
)
{
final
categories
=
response
.
data
!;
selectedIds
=
categories
.
listItems
?.
where
((
item
)
=>
item
.
subscribed
==
"1"
)
.
map
((
item
)
=>
item
.
categoryCode
??
''
)
.
where
((
code
)
=>
code
.
isNotEmpty
)
.
toList
()
.
toSet
()
??
<
String
>{};
interestedCategories
.
value
=
categories
;
}
else
{
onShowAlertError
?.
call
(
response
.
message
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
submitInterestedCategories
()
async
{
final
categories
=
selectedIds
.
toList
();
showLoading
();
try
{
final
response
=
await
client
.
submitCategorySubscribe
(
categories
.
join
(
','
));
hideLoading
();
onShowAlertError
?.
call
(
response
.
isSuccess
?
"Cập nhật sở thích thành công"
:
response
.
message
??
Constants
.
commonError
,
);
final
List
<
String
>
categoryCodes
=
interestedCategories
.
value
?.
listItems
?.
map
((
item
)
=>
item
.
categoryCode
??
''
)
.
where
((
code
)
=>
code
.
isNotEmpty
)
.
toList
()
??
[];
final
filteredList
=
categoryCodes
?.
where
((
item
)
=>
!
categories
.
contains
(
item
)).
toList
();
if
(
filteredList
==
null
||
filteredList
.
isEmpty
)
return
;
submitUnsubscribeInterestedCategories
(
filteredList
!);
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
submitUnsubscribeInterestedCategories
(
List
<
String
>
categories
)
async
{
final
_
=
await
client
.
submitCategoryUnsubscribeList
(
categories
.
join
(
','
));
}
}
lib/screen/interested_categories/models/interested_categories_model.dart
0 → 100644
View file @
417358c5
import
'package:json_annotation/json_annotation.dart'
;
part
'interested_categories_model.g.dart'
;
@JsonSerializable
()
class
InterestedCategoriesResponse
{
@JsonKey
(
name:
'list_items'
)
final
List
<
InterestedCategoryItem
>?
listItems
;
InterestedCategoriesResponse
({
this
.
listItems
});
factory
InterestedCategoriesResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$InterestedCategoriesResponseFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$InterestedCategoriesResponseToJson
(
this
);
}
@JsonSerializable
()
class
InterestedCategoryItem
{
final
String
?
id
;
final
String
?
subscribed
;
@JsonKey
(
name:
'category_code'
)
final
String
?
categoryCode
;
@JsonKey
(
name:
'category_name'
)
final
String
?
categoryName
;
@JsonKey
(
name:
'image_url'
)
final
String
?
imageUrl
;
InterestedCategoryItem
({
this
.
id
,
this
.
subscribed
,
this
.
categoryCode
,
this
.
categoryName
,
this
.
imageUrl
,
});
factory
InterestedCategoryItem
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$InterestedCategoryItemFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$InterestedCategoryItemToJson
(
this
);
}
lib/screen/interested_categories/models/interested_categories_model.g.dart
0 → 100644
View file @
417358c5
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'interested_categories_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
InterestedCategoriesResponse
_$InterestedCategoriesResponseFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
InterestedCategoriesResponse
(
listItems:
(
json
[
'list_items'
]
as
List
<
dynamic
>?)
?.
map
(
(
e
)
=>
InterestedCategoryItem
.
fromJson
(
e
as
Map
<
String
,
dynamic
>),
)
.
toList
(),
);
Map
<
String
,
dynamic
>
_$InterestedCategoriesResponseToJson
(
InterestedCategoriesResponse
instance
,
)
=>
<
String
,
dynamic
>{
'list_items'
:
instance
.
listItems
};
InterestedCategoryItem
_$InterestedCategoryItemFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
InterestedCategoryItem
(
id:
json
[
'id'
]
as
String
?,
subscribed:
json
[
'subscribed'
]
as
String
?,
categoryCode:
json
[
'category_code'
]
as
String
?,
categoryName:
json
[
'category_name'
]
as
String
?,
imageUrl:
json
[
'image_url'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$InterestedCategoryItemToJson
(
InterestedCategoryItem
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'subscribed'
:
instance
.
subscribed
,
'category_code'
:
instance
.
categoryCode
,
'category_name'
:
instance
.
categoryName
,
'image_url'
:
instance
.
imageUrl
,
};
lib/screen/invite_friend_campaign/invite_friend_campaign_screen.dart
View file @
417358c5
...
...
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import
'package:flutter/services.dart'
;
import
'package:flutter_contacts/flutter_contacts.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'package:mypoint_flutter_app/screen/invite_friend_campaign/popup_invite_friend_code.dart'
;
import
'package:share_plus/share_plus.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../widgets/image_loader.dart'
;
...
...
Prev
1
2
3
4
5
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