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
efb4662c
Commit
efb4662c
authored
Aug 08, 2025
by
DatHV
Browse files
update campaign 7day
parent
4c376d38
Changes
94
Show whitespace changes
Inline
Side-by-side
lib/screen/personal/personal_screen.dart
View file @
efb4662c
...
@@ -179,13 +179,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
...
@@ -179,13 +179,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
final
menuItems
=
[
final
menuItems
=
[
{
'icon'
:
Icons
.
monetization_on_outlined
,
'title'
:
'Săn điểm'
,
'type'
:
'APP_SCREEN_POINT_HUNTING'
},
{
'icon'
:
Icons
.
monetization_on_outlined
,
'title'
:
'Săn điểm'
,
'type'
:
'APP_SCREEN_POINT_HUNTING'
},
{
'icon'
:
Icons
.
check_box_outlined
,
'title'
:
'Check-in nhận quà'
,
'type'
:
'DAILY_CHECKIN'
},
{
'icon'
:
Icons
.
check_box_outlined
,
'title'
:
'Check-in nhận quà'
,
'type'
:
'DAILY_CHECKIN'
},
{
'icon'
:
Icons
.
emoji_events_outlined
,
'title'
:
'Bảng xếp hạng'
,
'type'
:
''
},
{
'icon'
:
Icons
.
emoji_events_outlined
,
'title'
:
'Bảng xếp hạng'
,
'type'
:
'
APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC
'
},
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
'icon'
:
Icons
.
receipt_long_outlined
,
'title'
:
'Lịch sử giao dịch'
,
'sectionDivider'
:
true
,
'type'
:
''
},
{
'icon'
:
Icons
.
receipt_long_outlined
,
'title'
:
'Lịch sử giao dịch'
,
'sectionDivider'
:
true
,
'type'
:
'
APP_SCREEN_TRANSACTION_HISTORIES
'
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử điểm'
,
'type'
:
''
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử điểm'
,
'type'
:
'
APP_SCREEN_SURVERY_APP
'
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử hoàn điểm'
,
'type'
:
'APP_SCREEN_REFUND_HISTORY'
},
{
'icon'
:
Icons
.
history_outlined
,
'title'
:
'Lịch sử hoàn điểm'
,
'type'
:
'APP_SCREEN_REFUND_HISTORY'
},
{
'icon'
:
Icons
.
account_balance_wallet_outlined
,
'title'
:
'Quản lý tài khoản/thẻ'
,
'type'
:
''
},
{
'icon'
:
Icons
.
account_balance_wallet_outlined
,
'title'
:
'Quản lý tài khoản/thẻ'
,
'type'
:
'
APP_SCREEN_ELECTRIC_BILL
'
},
{
'icon'
:
Icons
.
favorite_border
,
'title'
:
'Yêu thích'
,
'type'
:
''
},
{
'icon'
:
Icons
.
favorite_border
,
'title'
:
'Yêu thích'
,
'type'
:
'
APP_SCREEN_CATEGORY_TAB_FAVORITE
'
},
{
'icon'
:
Icons
.
shopping_bag_outlined
,
'title'
:
'Đơn mua'
,
'sectionDivider'
:
true
,
'type'
:
'APP_SCREEN_ORDER_MENU'
},
{
'icon'
:
Icons
.
shopping_bag_outlined
,
'title'
:
'Đơn mua'
,
'sectionDivider'
:
true
,
'type'
:
'APP_SCREEN_ORDER_MENU'
},
{
'icon'
:
Icons
.
info_outline
,
'title'
:
'Giới thiệu MyPoint'
,
'sectionDivider'
:
true
,
'type'
:
'VIEW_WEBSITE_PAGE'
},
{
'icon'
:
Icons
.
info_outline
,
'title'
:
'Giới thiệu MyPoint'
,
'sectionDivider'
:
true
,
'type'
:
'VIEW_WEBSITE_PAGE'
},
{
'icon'
:
Icons
.
headset_mic_outlined
,
'title'
:
'Hỗ trợ'
,
'type'
:
'APP_SCREEN_CUSTOMER_FEEDBACK'
},
{
'icon'
:
Icons
.
headset_mic_outlined
,
'title'
:
'Hỗ trợ'
,
'type'
:
'APP_SCREEN_CUSTOMER_FEEDBACK'
},
...
...
lib/screen/quiz_campaign/quiz_campaign_header.dart
0 → 100644
View file @
efb4662c
import
'package:flutter/material.dart'
;
import
'../../configs/callbacks.dart'
;
import
'../../widgets/back_button.dart'
;
class
QuizCampaignHeader
extends
StatelessWidget
{
final
int
currentIndex
;
final
int
total
;
final
VoidCallback
?
onBackPressed
;
const
QuizCampaignHeader
({
super
.
key
,
required
this
.
currentIndex
,
required
this
.
total
,
this
.
onBackPressed
,
});
@override
Widget
build
(
BuildContext
context
)
{
final
topSpace
=
MediaQuery
.
of
(
context
).
padding
.
top
;
return
Stack
(
children:
[
Container
(
padding:
EdgeInsets
.
only
(
top:
topSpace
,
left:
16
,
right:
16
),
color:
const
Color
(
0xFFFFF1F3
),
child:
Column
(
children:
[
Row
(
children:
[
CustomBackButton
(
onPressed:
onBackPressed
??
()
=>
Navigator
.
of
(
context
).
pop
(),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
_buildProgressBar
(
currentIndex
,
total
)),
const
SizedBox
(
width:
24
),
],
),
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Expanded
(
child:
const
Text
(
'Khảo sát
\n
nhận thưởng'
,
style:
TextStyle
(
fontSize:
24
,
fontWeight:
FontWeight
.
w800
,
color:
Color
(
0xFF3B0E0E
),
),
),
),
const
SizedBox
(
width:
12
),
Image
.
asset
(
'assets/images/ic_header_quiz_survey.png'
,
height:
180
,
fit:
BoxFit
.
contain
,
),
],
),
],
),
),
],
);
}
Widget
_buildProgressBar
(
int
current
,
int
total
)
{
return
Row
(
children:
List
.
generate
(
total
,
(
index
)
=>
Expanded
(
child:
Container
(
height:
6
,
margin:
EdgeInsets
.
only
(
left:
index
==
0
?
0
:
8
),
decoration:
BoxDecoration
(
color:
index
<=
current
?
const
Color
(
0xFFEC4A53
)
:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
3
),
),
),
),
),
);
}
}
lib/screen/quiz_campaign/quiz_campaign_model.dart
0 → 100644
View file @
efb4662c
import
'../../widgets/alert/popup_data_model.dart'
;
enum
SurveyQuestionType
{
textarea
,
radio
,
checkbox
;
String
get
textDes
{
switch
(
this
)
{
case
SurveyQuestionType
.
textarea
:
return
'Nhập câu trả lời dạng text'
;
case
SurveyQuestionType
.
radio
:
return
'Chọn 1 đáp án duy nhất'
;
case
SurveyQuestionType
.
checkbox
:
return
'Chọn nhiều đáp án'
;
}
}
}
SurveyQuestionType
?
_parseType
(
String
?
raw
)
{
if
(
raw
==
null
)
return
null
;
return
SurveyQuestionType
.
values
.
firstWhere
(
(
e
)
=>
e
.
name
==
raw
,
orElse:
()
=>
SurveyQuestionType
.
textarea
,
);
}
class
QuizCampaignSubmitResponseModel
{
final
PopupDataModel
?
popup
;
QuizCampaignSubmitResponseModel
({
this
.
popup
});
factory
QuizCampaignSubmitResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
QuizCampaignSubmitResponseModel
(
popup:
json
[
'popup'
]
!=
null
?
PopupDataModel
.
fromJson
(
json
[
'popup'
])
:
null
,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'popup'
:
popup
?.
toJson
(),
};
}
}
class
SurveyAnswerCampaignModel
{
int
?
id
;
String
?
value
;
String
?
placeholder
;
String
?
type
;
bool
?
isSelected
;
bool
?
required
;
SurveyQuestionType
?
get
qType
{
return
_parseType
(
type
);
}
SurveyAnswerCampaignModel
();
factory
SurveyAnswerCampaignModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
SurveyAnswerCampaignModel
()
..
id
=
json
[
'id'
]
..
value
=
json
[
'value'
]
..
placeholder
=
json
[
'placeholder'
]
..
type
=
json
[
'type'
]
..
isSelected
=
json
[
'isSelected'
]
..
required
=
json
[
'required'
];
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'id'
:
id
,
'value'
:
value
,
'placeholder'
:
placeholder
,
'type'
:
type
,
'isSelected'
:
isSelected
,
'required'
:
required
,
};
}
class
SurveyQuestionCampaignModel
{
int
id
;
bool
?
required
;
String
?
type
;
String
?
text
;
List
<
SurveyAnswerCampaignModel
>?
choices
;
String
?
textDesIndex
;
String
?
inputText
;
SurveyQuestionCampaignModel
({
required
this
.
id
,
this
.
required
,
this
.
type
,
this
.
text
,
this
.
choices
,
this
.
textDesIndex
,
this
.
inputText
,
});
bool
get
answered
{
if
(
qType
==
SurveyQuestionType
.
textarea
)
{
return
inputText
?.
isNotEmpty
??
false
;
}
else
if
(
qType
==
SurveyQuestionType
.
radio
||
qType
==
SurveyQuestionType
.
checkbox
)
{
return
choices
?.
any
((
e
)
=>
e
.
isSelected
==
true
)
??
false
;
}
return
false
;
}
SurveyQuestionType
?
get
qType
=>
_parseType
(
type
);
Map
<
String
,
dynamic
>
get
submitParamQuestion
{
final
List
<
dynamic
>
answers
=
[];
if
(
qType
==
SurveyQuestionType
.
textarea
)
{
answers
.
add
({
'text'
:
inputText
??
''
});
}
else
{
for
(
var
item
in
choices
??
[])
{
answers
.
add
({
'text'
:
item
.
value
??
''
});
}
}
return
{
'question_id'
:
id
,
'answers'
:
answers
,
};
}
factory
SurveyQuestionCampaignModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
SurveyQuestionCampaignModel
(
id:
json
[
'id'
],
required
:
json
[
'required'
],
type:
json
[
'type'
],
text:
json
[
'text'
],
choices:
(
json
[
'choices'
]
as
List
?)
?.
map
((
e
)
=>
SurveyAnswerCampaignModel
.
fromJson
(
e
))
.
toList
(),
inputText:
json
[
'input_text'
],
);
}
}
class
SurveyCampaignInfoModel
{
int
id
;
String
?
name
;
List
<
SurveyQuestionCampaignModel
>?
questions
;
SurveyCampaignInfoModel
({
required
this
.
id
,
this
.
name
,
this
.
questions
,
});
void
makeQuestionsTextDesIndex
()
{
final
total
=
questions
?.
length
??
0
;
for
(
var
i
=
0
;
i
<
total
;
i
++)
{
final
q
=
questions
![
i
];
final
des
=
'Câu
${i + 1}
/
$total
:
${q.qType?.textDes ?? ''}
'
;
q
.
textDesIndex
=
des
;
if
(
q
.
qType
==
SurveyQuestionType
.
textarea
&&
(
q
.
choices
?.
isEmpty
??
true
))
{
q
.
choices
=
[
SurveyAnswerCampaignModel
()];
}
}
}
Map
<
String
,
dynamic
>
get
submitParam
{
return
{
'quiz_id'
:
id
,
'answers'
:
questions
?.
map
((
e
)
=>
e
.
submitParamQuestion
).
toList
()
??
[],
};
}
factory
SurveyCampaignInfoModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
SurveyCampaignInfoModel
(
id:
json
[
'id'
],
name:
json
[
'name'
],
questions:
(
json
[
'questions'
]
as
List
?)
?.
map
((
e
)
=>
SurveyQuestionCampaignModel
.
fromJson
(
e
))
.
toList
(),
);
}
}
lib/screen/quiz_campaign/quiz_campaign_screen.dart
0 → 100644
View file @
efb4662c
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/collection_extension.dart'
;
import
'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_header.dart'
;
import
'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_model.dart'
;
import
'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_viewmodel.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
class
SurveyQuestionScreen
extends
BaseScreen
{
const
SurveyQuestionScreen
({
super
.
key
});
@override
State
<
SurveyQuestionScreen
>
createState
()
=>
_SurveyQuestionScreenState
();
}
class
_SurveyQuestionScreenState
extends
BaseState
<
SurveyQuestionScreen
>
with
BasicState
{
late
final
QuizCampaignViewModel
_viewModel
;
int
currentIndex
=
0
;
FocusNode
?
_textFocusNode
;
@override
void
initState
()
{
super
.
initState
();
_textFocusNode
=
FocusNode
();
String
?
quizId
;
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
quizId
=
args
[
'quizId'
];
}
if
(
quizId
==
null
&&
quizId
==
null
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
return
;
}
_viewModel
=
Get
.
put
(
QuizCampaignViewModel
(
quizId:
quizId
));
_viewModel
.
onShowAlertError
=
(
message
,
{
shouldQuitScreen
=
false
})
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
,
onConfirmed:
shouldQuitScreen
?
_onQuitScreen
:
null
,
);
}
};
_viewModel
.
quizCampaignSubmitResponse
=
(
popup
)
{
showPopup
(
data:
popup
);
};
_viewModel
.
getQuizCampaignDetail
();
}
_onQuitScreen
()
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
}
@override
void
dispose
()
{
_textFocusNode
?.
dispose
();
super
.
dispose
();
}
@override
Widget
createBody
()
{
final
textController
=
TextEditingController
();
final
bottomSpace
=
MediaQuery
.
of
(
context
).
padding
.
bottom
;
return
Scaffold
(
body:
Obx
(()
{
final
questions
=
_viewModel
.
surveyData
.
value
?.
questions
??
[];
final
currentQuestion
=
questions
.
safe
(
currentIndex
);
final
isLastQuestion
=
currentIndex
==
questions
.
length
-
1
;
final
qType
=
currentQuestion
?.
qType
;
if
(
qType
==
SurveyQuestionType
.
textarea
)
{
textController
.
text
=
currentQuestion
?.
inputText
??
''
;
}
if
(
currentQuestion
==
null
)
{
return
const
EmptyWidget
();
}
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
QuizCampaignHeader
(
currentIndex:
currentIndex
,
total:
questions
.
length
,
onBackPressed:
_showAlertConfirmQuitSurvey
,
),
Expanded
(
child:
QuizCampaignContainer
(
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
only
(
bottom:
8
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
'Câu
${currentIndex + 1}
/
${questions.length}
:
${qType?.textDes ?? ''}
'
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
,
color:
Colors
.
black54
),
),
const
SizedBox
(
height:
8
),
Text
(
currentQuestion
.
text
??
''
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
height:
12
),
if
(
qType
==
SurveyQuestionType
.
textarea
)
TextField
(
focusNode:
_textFocusNode
,
controller:
textController
,
maxLines:
10
,
decoration:
InputDecoration
(
hintText:
'Nhập câu trả lời của bạn'
,
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
(
color:
Colors
.
grey
.
shade300
),
),
enabledBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
(
color:
Colors
.
grey
.
shade300
),
),
focusedBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
const
BorderSide
(
color:
Color
(
0xFFEC4A53
),
width:
1.5
),
),
),
onChanged:
(
value
)
{
currentQuestion
?.
inputText
=
value
;
},
),
...
List
.
generate
(
currentQuestion
.
choices
?.
length
??
0
,
(
index
)
{
final
choice
=
currentQuestion
.
choices
![
index
];
final
isSelected
=
choice
.
isSelected
==
true
;
return
Padding
(
padding:
const
EdgeInsets
.
only
(
bottom:
8.0
),
child:
InkWell
(
onTap:
()
{
setState
(()
{
if
(
qType
==
SurveyQuestionType
.
radio
)
{
for
(
final
c
in
currentQuestion
.
choices
!)
{
c
.
isSelected
=
false
;
}
choice
.
isSelected
=
true
;
}
else
if
(
qType
==
SurveyQuestionType
.
checkbox
)
{
choice
.
isSelected
=
!(
choice
.
isSelected
??
false
);
}
});
},
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
border:
Border
.
all
(
color:
isSelected
?
BaseColor
.
primary400
:
Colors
.
grey
.
shade300
,
width:
1.5
,
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
if
(
qType
==
SurveyQuestionType
.
radio
)
Radio
<
String
>(
activeColor:
BaseColor
.
primary400
,
value:
choice
.
value
??
''
,
groupValue:
currentQuestion
.
choices
!.
firstWhereOrNull
((
e
)
=>
e
.
isSelected
==
true
)?.
value
,
onChanged:
(
_
)
{},
),
if
(
qType
==
SurveyQuestionType
.
checkbox
)
Checkbox
(
value:
isSelected
,
onChanged:
(
_
)
{},
activeColor:
BaseColor
.
primary400
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
choice
.
value
??
''
,
style:
const
TextStyle
(
fontSize:
16
))),
],
),
),
),
);
}),
],
),
),
),
),
Padding
(
padding:
EdgeInsets
.
only
(
left:
20
,
right:
20
,
bottom:
bottomSpace
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
OutlinedButton
(
onPressed:
currentIndex
>
0
?
_prev
:
null
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
white
,
foregroundColor:
BaseColor
.
primary400
,
),
child:
Text
(
'Trước'
,
style:
TextStyle
(
fontSize:
16
)),
),
ElevatedButton
(
onPressed:
(
currentQuestion
.
required
==
true
&&
!
currentQuestion
.
answered
)
?
null
:
_next
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isLastQuestion
?
Colors
.
redAccent
:
Colors
.
white
,
// Màu nền
foregroundColor:
isLastQuestion
?
Colors
.
white
:
BaseColor
.
primary400
,
// Màu chữ/icon
),
child:
Text
(
isLastQuestion
?
'Hoàn thành'
:
'Tiếp'
,
style:
TextStyle
(
fontSize:
16
)),
),
],
),
),
],
);
}),
backgroundColor:
const
Color
(
0xFFFFF1F3
),
);
}
_next
()
{
if
(
currentIndex
<
(
_viewModel
.
surveyData
.
value
?.
questions
?.
length
??
0
)
-
1
)
{
setState
(()
=>
currentIndex
++);
}
else
{
_textFocusNode
?.
unfocus
();
Future
.
delayed
(
Duration
(
milliseconds:
100
),
()
{
_showAlertConfirmSubmit
();
});
}
}
void
_prev
()
{
if
(
currentIndex
>
0
)
{
setState
(()
=>
currentIndex
--);
}
}
_showAlertConfirmSubmit
()
{
final
dataAlert
=
DataAlertModel
(
title:
"Xác nhận"
,
description:
"Bạn chắc chắn muốn nộp khảo sát? Sau khi gửi, bạn sẽ không thể thay đổi câu trả lời."
,
localHeaderImage:
"assets/images/ic_pipi_05.png"
,
buttons:
[
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
{
Get
.
back
();
_viewModel
.
quizCampaignSubmit
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Huỷ"
,
onPressed:
()
=>
Get
.
back
(),
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
),
],
);
showAlert
(
data:
dataAlert
);
}
_showAlertConfirmQuitSurvey
()
{
final
dataAlert
=
DataAlertModel
(
description:
"Có vẻ bạn chưa hoàn thành nhiệm vụ rồi. Tiếp tục nhé!!"
,
localHeaderImage:
"assets/images/ic_pipi_03.png"
,
buttons:
[
AlertButton
(
text:
"Thực hiện"
,
onPressed:
()
{
Get
.
back
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Thoát"
,
onPressed:
()
{
Get
.
back
();
Get
.
back
();
},
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
,
),
],
);
showAlert
(
data:
dataAlert
);
}
}
extension
FirstWhereOrNullExtension
<
E
>
on
Iterable
<
E
>
{
E
?
firstWhereOrNull
(
bool
Function
(
E
)
test
)
{
for
(
var
element
in
this
)
{
if
(
test
(
element
))
return
element
;
}
return
null
;
}
}
class
QuizCampaignContainer
extends
StatelessWidget
{
final
Widget
child
;
const
QuizCampaignContainer
({
super
.
key
,
required
this
.
child
});
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
margin:
EdgeInsets
.
only
(
left:
16
,
right:
16
,
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
// border: Border.all(color: Colors.redAccent.shade100, width: 1),
),
child:
child
,
);
}
}
lib/screen/quiz_campaign/quiz_campaign_viewmodel.dart
0 → 100644
View file @
efb4662c
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/configs/constants.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_model.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../widgets/alert/popup_data_model.dart'
;
class
QuizCampaignViewModel
extends
RestfulApiViewModel
{
var
surveyData
=
Rxn
<
SurveyCampaignInfoModel
>();
String
quizId
;
void
Function
(
String
message
,
{
bool
shouldQuitScreen
})?
onShowAlertError
;
void
Function
(
PopupDataModel
data
)?
quizCampaignSubmitResponse
;
QuizCampaignViewModel
({
required
this
.
quizId
});
Future
<
void
>
getQuizCampaignDetail
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
getCampaignQuizSurvey
(
quizId
);
hideLoading
();
surveyData
.
value
=
response
.
data
;
if
(
surveyData
.
value
==
null
)
{
onShowAlertError
?.
call
(
Constants
.
commonError
,
shouldQuitScreen:
true
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
Future
<
void
>
quizCampaignSubmit
()
async
{
showLoading
();
final
body
=
surveyData
.
value
?.
submitParam
??
{};
try
{
final
response
=
await
client
.
quizSubmitCampaign
(
quizId
,
body
);
hideLoading
();
final
popup
=
response
.
data
?.
popup
;
if
(
popup
!=
null
)
{
quizCampaignSubmitResponse
?.
call
(
popup
);
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
Constants
.
commonError
);
}
}
}
\ No newline at end of file
lib/screen/splash/splash_screen_viewmodel.dart
View file @
efb4662c
...
@@ -54,7 +54,6 @@ class SplashScreenViewModel extends RestfulApiViewModel {
...
@@ -54,7 +54,6 @@ class SplashScreenViewModel extends RestfulApiViewModel {
});
});
}
}
}
}
class
EmptyCodable
{
class
EmptyCodable
{
EmptyCodable
.
fromJson
(
dynamic
json
);
EmptyCodable
.
fromJson
(
dynamic
json
);
...
...
lib/screen/traffic_service/traffic_service_certificate_screen.dart
0 → 100644
View file @
efb4662c
import
'dart:io'
;
import
'package:file_saver/file_saver.dart'
;
import
'package:flutter/material.dart'
;
import
'package:share_plus/share_plus.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:path_provider/path_provider.dart'
;
import
'package:http/http.dart'
as
http
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'dart:typed_data'
as
typed_data
;
class
TrafficServiceCertificateScreen
extends
BaseScreen
{
final
String
urlView
;
final
String
urlDownload
;
final
String
licensePlate
;
const
TrafficServiceCertificateScreen
({
super
.
key
,
required
this
.
urlView
,
required
this
.
urlDownload
,
required
this
.
licensePlate
,
});
@override
State
<
TrafficServiceCertificateScreen
>
createState
()
=>
_TrafficServiceCertificateScreenState
();
}
class
_TrafficServiceCertificateScreenState
extends
BaseState
<
TrafficServiceCertificateScreen
>
with
BasicState
{
late
final
WebViewController
_controller
;
@override
void
initState
()
{
super
.
initState
();
showLoading
();
_controller
=
WebViewController
()
..
loadRequest
(
Uri
.
parse
(
widget
.
urlView
))
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
..
setNavigationDelegate
(
NavigationDelegate
(
onPageFinished:
(
_
)
async
{
hideLoading
();
},
onWebResourceError:
(
error
)
{
hideLoading
();
},
),
);
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Giấy chứng nhận cứu hộ VNTRA"
),
body:
WebViewWidget
(
controller:
_controller
),
bottomNavigationBar:
_buildBottomButtonEditMode
(),
);
}
Widget
_buildBottomButtonEditMode
()
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black54
,
blurRadius:
8
,
offset:
Offset
(
0
,
4
))],
),
child:
SafeArea
(
top:
false
,
child:
SizedBox
(
width:
double
.
infinity
,
height:
48
,
child:
ElevatedButton
(
onPressed:
_savePdfToFiles
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
'Lưu file'
,
style:
TextStyle
(
fontSize:
18
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
),
),
),
);
}
// Future<void> _savePdfToFiles() async {
// final url = widget.urlDownload;
// final licensePlate = widget.licensePlate;
// try {
// final response = await http.get(Uri.parse(url));
// if (response.statusCode == 200) {
// typed_data.Uint8List bytes = response.bodyBytes;
// final fileName = 'MyPoint-Cer-$licensePlate.pdf';
//
// await FileSaver.instance.saveFile(
// name: fileName,
// bytes: bytes,
// ext: 'pdf',
// mimeType: MimeType.pdf,
// );
// } else {
// print("Tải file thất bại");
// }
// } catch (e) {
// print("Lỗi: $e");
// }
// }
Future
<
void
>
_savePdfToFiles
()
async
{
final
url
=
widget
.
urlDownload
;
final
licensePlate
=
widget
.
licensePlate
;
try
{
final
response
=
await
http
.
get
(
Uri
.
parse
(
url
));
if
(
response
.
statusCode
==
200
)
{
typed_data
.
Uint8List
bytes
=
response
.
bodyBytes
;
final
dir
=
await
getTemporaryDirectory
();
final
filePath
=
'
${dir.path}
/MyPoint-Cer-
$licensePlate
.pdf'
;
final
file
=
File
(
filePath
);
await
file
.
writeAsBytes
(
bytes
);
await
Share
.
shareXFiles
([
XFile
(
filePath
)],
text:
'Giấy chứng nhận cứu hộ'
);
}
else
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Tải file thất bại'
)),
);
}
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'Lỗi:
$e
'
)),
);
}
}
Future
<
void
>
_savePdf
()
async
{
final
url
=
widget
.
urlDownload
;
final
licensePlate
=
widget
.
licensePlate
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Đang tải PDF...'
)),
);
try
{
final
response
=
await
http
.
get
(
Uri
.
parse
(
url
));
if
(
response
.
statusCode
==
200
)
{
final
dir
=
await
getApplicationDocumentsDirectory
();
String
baseName
=
'MyPoint-Cer-
$licensePlate
.pdf'
;
String
path
=
'
${dir.path}
/
$baseName
'
;
int
count
=
0
;
while
(
File
(
path
).
existsSync
())
{
path
=
'
${dir.path}
/MyPoint-Cer-
$licensePlate
-
${count++}
.pdf'
;
}
final
file
=
File
(
path
);
await
file
.
writeAsBytes
(
response
.
bodyBytes
);
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'Lưu file thành công:
\n
${file.path}
'
)),
);
}
else
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Tải file thất bại'
)),
);
}
}
catch
(
e
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'Lỗi:
$e
'
)),
);
}
}
}
lib/screen/traffic_service/traffic_service_detail_screen.dart
0 → 100644
View file @
efb4662c
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_certificate_screen.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_model.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_viewmodel.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
class
TrafficServiceDetailScreen
extends
StatefulWidget
{
const
TrafficServiceDetailScreen
({
super
.
key
});
@override
State
<
TrafficServiceDetailScreen
>
createState
()
=>
_TrafficServiceDetailScreenState
();
}
class
_TrafficServiceDetailScreenState
extends
State
<
TrafficServiceDetailScreen
>
{
final
TrafficServiceViewModel
_viewModel
=
Get
.
put
(
TrafficServiceViewModel
());
@override
void
initState
()
{
super
.
initState
();
int
?
serviceId
;
TrafficServiceDetailModel
?
data
;
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
serviceId
=
args
[
'serviceId'
];
data
=
args
[
'data'
];
}
if
(
serviceId
==
null
&&
data
==
null
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
return
;
}
_viewModel
.
trafficServiceDetail
.
value
=
data
;
if
(
serviceId
!=
null
)
{
_viewModel
.
getTrafficServiceDetail
(
serviceId
.
toString
());
}
// _viewModel.onShowAlertError = (message) {
// if (message.isNotEmpty) {
// showAlertError(content: message);
// }
// };
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Thông tin chi tiết"
),
body:
Obx
(()
{
final
model
=
_viewModel
.
trafficServiceDetail
.
value
;
if
(
model
==
null
)
{
return
const
Center
(
child:
EmptyWidget
());
}
return
Column
(
children:
[
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_sectionTitle
(
"Thông tin xe"
),
_rowItem
(
"Biển số xe"
,
model
.
licensePlate
??
''
),
const
Divider
(
color:
Colors
.
black12
),
_sectionTitle
(
"Thông tin chủ xe"
),
_rowItem
(
"Họ và tên"
,
model
.
ownerName
??
''
),
_rowItem
(
"Số điện thoại"
,
model
.
phoneNumber
??
''
),
const
Divider
(
color:
Colors
.
black12
),
_sectionTitle
(
"Thông tin gói dịch vụ"
),
_rowItem
(
"Tên gói"
,
model
.
packageName
??
''
),
_rowItem
(
"Hiệu lực gói dịch vụ"
,
model
.
dateDes
),
GestureDetector
(
onTap:
()
async
{
final
Uri
phoneUri
=
Uri
(
scheme:
'tel'
,
path:
model
.
hotline
);
if
(
await
canLaunchUrl
(
phoneUri
))
{
await
launchUrl
(
phoneUri
);
}
},
child:
_rowItem
(
"SĐT cứu hộ"
,
model
.
hotline
??
''
,
color:
Colors
.
red
,
icon:
Icons
.
phone
),
),
const
Divider
(
color:
Colors
.
black12
),
],
),
),
],
);
}),
bottomNavigationBar:
_buildBottomButtonEditMode
(),
);
}
Widget
_sectionTitle
(
String
title
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Text
(
title
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
18
)),
);
}
Widget
_rowItem
(
String
label
,
String
value
,
{
Color
?
color
,
IconData
?
icon
})
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
4
),
child:
Row
(
children:
[
Expanded
(
child:
Text
(
label
,
style:
const
TextStyle
(
color:
Colors
.
black54
,
fontSize:
14
))),
if
(
icon
!=
null
)
Icon
(
icon
,
color:
color
??
Colors
.
black
),
const
SizedBox
(
width:
4
),
Text
(
value
,
style:
TextStyle
(
color:
color
??
Colors
.
black87
,
fontSize:
14
)),
],
),
);
}
Widget
_buildBottomButtonEditMode
()
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
boxShadow:
[
BoxShadow
(
color:
Colors
.
black54
,
blurRadius:
8
,
offset:
Offset
(
0
,
4
))],
),
child:
SafeArea
(
top:
false
,
child:
SizedBox
(
width:
double
.
infinity
,
height:
48
,
child:
ElevatedButton
(
onPressed:
()
{
Get
.
to
(
TrafficServiceCertificateScreen
(
urlView:
_viewModel
.
trafficServiceDetail
.
value
?.
urlView
??
''
,
urlDownload:
_viewModel
.
trafficServiceDetail
.
value
?.
urlDownload
??
''
,
licensePlate:
_viewModel
.
trafficServiceDetail
.
value
?.
licensePlate
??
''
,
));
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
'Xem giấy chứng nhận'
,
style:
TextStyle
(
fontSize:
18
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
),
),
),
);
}
}
lib/screen/traffic_service/traffic_service_model.dart
0 → 100644
View file @
efb4662c
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
enum
SortFilter
{
asc
,
desc
}
class
HeaderFilterOrderModel
{
String
title
;
SortFilter
?
sort
;
String
?
expired
;
double
rateWidth
;
String
suffixChecking
;
bool
?
selected
;
HeaderFilterOrderModel
({
required
this
.
title
,
this
.
sort
,
this
.
expired
,
this
.
rateWidth
=
1.0
,
required
this
.
suffixChecking
,
this
.
selected
,
});
Map
<
String
,
dynamic
>
get
params
{
if
(
sort
!=
null
)
{
return
{
'order_checkup'
:
sort
!.
name
};
}
return
{
'expired'
:
expired
??
''
};
}
String
get
eventNameTracking
{
if
(
sort
==
null
)
{
return
'soskdt_
${suffixChecking}
'
;
}
return
'soskdt_
${suffixChecking}
_
${sort!.name}
'
;
}
String
get
eventNameTrackingDvCar
=>
'dvcar_
${suffixChecking}
'
;
}
class
TrafficServiceResponseModel
{
int
?
total
;
List
<
TrafficServiceDetailModel
>?
products
;
TrafficServiceResponseModel
({
this
.
total
,
this
.
products
});
factory
TrafficServiceResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
TrafficServiceResponseModel
(
total:
json
[
'total'
],
products:
(
json
[
'products'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
TrafficServiceDetailModel
.
fromJson
(
e
))
.
toList
(),
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'total'
:
total
,
'products'
:
products
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
};
}
class
TrafficServiceDetailModel
{
int
?
itemId
;
String
?
licensePlate
;
String
?
ownerName
;
String
?
phoneNumber
;
String
?
packageName
;
String
?
hotline
;
String
?
startTime
;
String
?
endTime
;
String
?
updatedAt
;
ButtonConfigModel
?
buyMoreNote
;
ActiveTextConfig
?
active
;
String
?
urlDownload
;
String
?
urlView
;
List
<
ProductMediaItem
>?
media
;
TrafficServiceDetailModel
({
this
.
itemId
,
this
.
licensePlate
,
this
.
ownerName
,
this
.
phoneNumber
,
this
.
packageName
,
this
.
hotline
,
this
.
startTime
,
this
.
endTime
,
this
.
updatedAt
,
this
.
buyMoreNote
,
this
.
active
,
this
.
urlDownload
,
this
.
urlView
,
this
.
media
,
});
factory
TrafficServiceDetailModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
TrafficServiceDetailModel
(
itemId:
json
[
'item_id'
],
licensePlate:
json
[
'license_plate'
],
ownerName:
json
[
'owner_name'
],
phoneNumber:
json
[
'phone_number'
],
packageName:
json
[
'package_name'
],
hotline:
json
[
'hotline'
],
startTime:
json
[
'start_time'
],
endTime:
json
[
'end_time'
],
updatedAt:
json
[
'updated_at'
],
buyMoreNote:
json
[
'buy_more_note'
]
!=
null
?
ButtonConfigModel
.
fromJson
(
json
[
'buy_more_note'
])
:
null
,
active:
json
[
'active'
]
!=
null
?
ActiveTextConfig
.
fromJson
(
json
[
'active'
])
:
null
,
urlDownload:
json
[
'url_download'
],
urlView:
json
[
'url_view'
],
media:
(
json
[
'media'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
ProductMediaItem
.
fromJson
(
e
))
.
toList
(),
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'item_id'
:
itemId
,
'license_plate'
:
licensePlate
,
'owner_name'
:
ownerName
,
'phone_number'
:
phoneNumber
,
'package_name'
:
packageName
,
'hotline'
:
hotline
,
'start_time'
:
startTime
,
'end_time'
:
endTime
,
'updated_at'
:
updatedAt
,
'buy_more_note'
:
buyMoreNote
?.
toJson
(),
'active'
:
active
?.
toJson
(),
'url_download'
:
urlDownload
,
'url_view'
:
urlView
,
'media'
:
media
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
};
String
get
dateDes
{
final
start
=
(
startTime
??
''
).
toDate
()?.
toFormattedString
()
??
''
;
final
end
=
(
endTime
??
''
).
toDate
()?.
toFormattedString
()
??
''
;
return
'
$start
-
$end
'
;
}
}
class
ActiveTextConfig
{
String
?
text
;
String
?
textColor
;
String
?
bgColor
;
ActiveTextConfig
({
this
.
text
,
this
.
textColor
,
this
.
bgColor
});
factory
ActiveTextConfig
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
ActiveTextConfig
(
text:
json
[
'text'
],
textColor:
json
[
'text_color'
],
bgColor:
json
[
'bg_color'
],
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'text'
:
text
,
'text_color'
:
textColor
,
'bg_color'
:
bgColor
,
};
}
class
ProductMediaItem
{
String
?
name
;
String
?
url
;
String
?
rawType
;
ProductMediaItem
({
this
.
name
,
this
.
url
,
this
.
rawType
});
factory
ProductMediaItem
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
ProductMediaItem
(
name:
json
[
'name'
],
url:
json
[
'url'
],
rawType:
json
[
'type'
],
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'name'
:
name
,
'url'
:
url
,
'type'
:
rawType
,
};
String
?
get
type
=>
rawType
;
}
class
ButtonConfigModel
{
String
?
text
;
String
?
action
;
ButtonConfigModel
({
this
.
text
,
this
.
action
});
factory
ButtonConfigModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
ButtonConfigModel
(
text:
json
[
'text'
],
action:
json
[
'action'
],
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'text'
:
text
,
'action'
:
action
,
};
}
\ No newline at end of file
lib/screen/traffic_service/traffic_service_screen.dart
0 → 100644
View file @
efb4662c
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_detail_screen.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_viewmodel.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../extensions/date_format.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
class
TrafficServiceScreen
extends
StatefulWidget
{
const
TrafficServiceScreen
({
super
.
key
});
@override
State
<
TrafficServiceScreen
>
createState
()
=>
_TrafficServiceScreenState
();
}
class
_TrafficServiceScreenState
extends
State
<
TrafficServiceScreen
>
{
final
TrafficServiceViewModel
_viewModel
=
Get
.
put
(
TrafficServiceViewModel
());
@override
void
initState
()
{
super
.
initState
();
_viewModel
.
getTrafficServiceData
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
tags
=
_viewModel
.
headerFilterOrder
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Dịch vụ giao thông"
),
body:
Column
(
children:
[
const
SizedBox
(
height:
8
),
Obx
(()
=>
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
children:
List
.
generate
(
tags
.
length
,
(
index
)
{
final
isSelected
=
index
==
_viewModel
.
selectedIndex
.
value
;
return
GestureDetector
(
onTap:
()
{
if
(
_viewModel
.
selectedIndex
.
value
==
index
)
return
;
setState
(()
{
_viewModel
.
selectedIndex
.
value
=
index
;
_viewModel
.
getTrafficServiceData
();
});
},
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
4
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
isSelected
?
BaseColor
.
primary500
:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Text
(
tags
[
index
].
title
,
style:
TextStyle
(
color:
isSelected
?
BaseColor
.
primary500
:
Colors
.
black87
),
),
),
);
}),
),
),
const
Divider
(
height:
14
,
color:
Colors
.
black12
),
const
SizedBox
(
height:
8
),
Expanded
(
child:
Obx
(()
{
final
products
=
_viewModel
.
trafficData
.
value
?.
products
??
[];
return
products
.
isEmpty
?
Center
(
child:
EmptyWidget
())
:
ListView
.
builder
(
itemCount:
products
.
length
,
itemBuilder:
(
context
,
index
)
{
final
item
=
products
[
index
];
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
4
),
child:
Container
(
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ListTile
(
onTap:
()
{
print
(
'Tapped on item:
${item.licensePlate}
'
);
Get
.
toNamed
(
trafficServiceDetailScreen
,
arguments:
{
'serviceId'
:
item
.
itemId
});
},
leading:
SizedBox
(
width:
60
,
// <= giới hạn rõ
height:
60
,
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
loadNetworkImage
(
url:
item
.
media
?.
firstOrNull
?.
url
??
''
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/bg_default_11.png'
,
),
),
),
title:
Text
(
item
.
licensePlate
??
''
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
subtitle:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
SizedBox
(
height:
4
),
Text
(
item
.
packageName
??
''
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w500
,
),
),
const
SizedBox
(
height:
4
),
Text
(
(
item
.
endTime
??
''
).
toDate
()?.
toFormattedString
()
??
''
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w500
,
),
),
const
SizedBox
(
height:
4
),
Text
(
'Cập nhật lúc
${(item.updatedAt ?? '').toDate()?.toFormattedString(format: DateFormat.ddMMyyyyhhmm) ?? ''}
'
,
style:
const
TextStyle
(
fontSize:
11
,
color:
Colors
.
black54
),
),
],
),
trailing:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
4
),
decoration:
BoxDecoration
(
color:
Colors
.
green
.
shade50
,
borderRadius:
BorderRadius
.
circular
(
4
),
),
child:
Text
(
item
.
active
?.
text
??
''
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
green
,
fontWeight:
FontWeight
.
w500
),
),
),
),
),
);
},
);
}),
),
],
),
);
}
}
lib/screen/traffic_service/traffic_service_viewmodel.dart
0 → 100644
View file @
efb4662c
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/configs/constants.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:mypoint_flutter_app/screen/traffic_service/traffic_service_model.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
class
TrafficServiceViewModel
extends
RestfulApiViewModel
{
var
trafficData
=
Rxn
<
TrafficServiceResponseModel
>();
void
Function
(
String
message
)?
onShowAlertError
;
var
trafficServiceDetail
=
Rxn
<
TrafficServiceDetailModel
>();
RxInt
selectedIndex
=
0
.
obs
;
List
<
HeaderFilterOrderModel
>
get
headerFilterOrder
{
return
[
HeaderFilterOrderModel
(
title:
'Tất cả'
,
suffixChecking:
'tatca'
,
selected:
true
,
),
HeaderFilterOrderModel
(
title:
'Hiệu lực'
,
suffixChecking:
'hieuluc'
,
),
HeaderFilterOrderModel
(
title:
'Không hiệu lực'
,
expired:
"true"
,
suffixChecking:
'khonghieuluc'
,
),
];
}
Future
<
void
>
getTrafficServiceData
()
async
{
var
body
=
headerFilterOrder
[
selectedIndex
.
value
].
params
;
body
[
'page'
]
=
1
;
body
[
'size'
]
=
10000
;
showLoading
();
try
{
final
response
=
await
client
.
getProductVnTraSold
(
body
);
hideLoading
();
if
(
response
.
isSuccess
)
{
trafficData
.
value
=
response
.
data
;
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
Future
<
void
>
getTrafficServiceDetail
(
String
id
)
async
{
showLoading
();
try
{
final
response
=
await
client
.
getDetailMyPackageVnTra
(
id
);
hideLoading
();
if
(
response
.
isSuccess
)
{
trafficServiceDetail
.
value
=
response
.
data
;
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
}
\ No newline at end of file
lib/screen/transaction/history/transaction_category_model.dart
0 → 100644
View file @
efb4662c
class
TransactionCategoryModel
{
int
?
id
;
String
?
code
;
String
?
name
;
bool
_isSelected
=
false
;
bool
get
isSelected
=>
_isSelected
;
set
isSelected
(
bool
value
)
=>
_isSelected
=
value
;
TransactionCategoryModel
({
this
.
id
,
this
.
code
,
this
.
name
,
bool
?
isSelected
,
})
:
_isSelected
=
isSelected
??
false
;
factory
TransactionCategoryModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
TransactionCategoryModel
(
id:
json
[
'id'
]
as
int
?,
code:
json
[
'code'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
isSelected:
json
[
'_isSelected'
]
as
bool
?
??
false
,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'id'
:
id
,
'code'
:
code
,
'name'
:
name
,
'_isSelected'
:
_isSelected
,
};
}
}
lib/screen/transaction/history/transaction_history_detail_screen.dart
View file @
efb4662c
...
@@ -121,12 +121,31 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
...
@@ -121,12 +121,31 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
const
SizedBox
(
height:
8
),
const
SizedBox
(
height:
8
),
const
Text
(
"Thanh toán mua ưu đãi"
,
style:
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
bold
)),
const
Text
(
"Thanh toán mua ưu đãi"
,
style:
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
bold
)),
const
SizedBox
(
height:
4
),
const
SizedBox
(
height:
4
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
if
((
data
.
payCash
??
''
).
isNotEmpty
)
Text
(
'
${data.payCash}
'
,
style:
const
TextStyle
(
fontSize:
20
,
color:
Colors
.
red
,
fontWeight:
FontWeight
.
bold
),
),
if
((
data
.
payCash
??
''
).
isNotEmpty
&&
(
data
.
payPoint
??
''
).
isNotEmpty
)
const
SizedBox
(
width:
24
),
if
((
data
.
payPoint
??
''
).
isNotEmpty
)
Row
(
children:
[
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
24
,
height:
24
),
const
SizedBox
(
width:
4
),
Text
(
Text
(
data
.
payCash
?.
isNotEmpty
==
true
?
'
${data.payCash}
'
:
data
.
payPoint
??
'0
đ
'
,
data
.
payPoint
??
'0'
,
style:
const
TextStyle
(
fontSize:
20
,
color:
Colors
.
red
,
fontWeight:
FontWeight
.
bold
),
style:
const
TextStyle
(
fontSize:
20
,
color:
Colors
.
red
,
fontWeight:
FontWeight
.
bold
),
),
),
],
],
),
),
],
),
],
),
),
),
);
);
}
}
...
@@ -232,15 +251,17 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
...
@@ -232,15 +251,17 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
minimum:
EdgeInsets
.
only
(
bottom:
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
),
minimum:
EdgeInsets
.
only
(
bottom:
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
),
child:
Container
(
child:
Container
(
width:
double
.
infinity
,
width:
double
.
infinity
,
padding:
const
EdgeInsets
.
all
(
1
6
),
padding:
const
EdgeInsets
.
all
(
1
2
),
child:
Column
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
children:
[
if
(
transaction
.
titleRedButton
!=
null
)
if
(
transaction
.
titleRedButton
!=
null
)
ElevatedButton
(
ElevatedButton
(
onPressed:
()
{
onPressed:
()
{
final
finish
=
transaction
.
directionScreenRedButton
?.
begin
();
if
(
finish
!=
true
)
{
Get
.
until
((
route
)
=>
Get
.
currentRoute
==
mainScreen
);
Get
.
until
((
route
)
=>
Get
.
currentRoute
==
mainScreen
);
// Navigator.of(context).pop();
}
},
},
style:
ElevatedButton
.
styleFrom
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary600
,
backgroundColor:
BaseColor
.
primary600
,
...
...
lib/screen/transaction/history/transaction_history_model.dart
View file @
efb4662c
import
'package:json_annotation/json_annotation.dart'
;
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/screen/transaction/history/transaction_history_emun.dart'
;
import
'package:mypoint_flutter_app/screen/transaction/history/transaction_history_emun.dart'
;
import
'../../../directional/directional_action_type.dart'
;
import
'../../../directional/directional_screen.dart'
;
import
'../../../directional/directional_screen.dart'
;
import
'../../voucher/models/product_type.dart'
;
import
'../../voucher/models/product_type.dart'
;
...
@@ -91,27 +92,36 @@ class TransactionHistoryModel {
...
@@ -91,27 +92,36 @@ class TransactionHistoryModel {
// String? get iconSupport =>
// String? get iconSupport =>
// statusT == TransactionStatusOrder.failed ? 'ic_support_transaction' : null;
// statusT == TransactionStatusOrder.failed ? 'ic_support_transaction' : null;
// DirectionalScreen? get directionScreenRedButton {
DirectionalScreen
?
get
directionScreenRedButton
{
// switch (statusT) {
switch
(
statusT
)
{
// case TransactionStatusOrder.success:
case
TransactionStatusOrder
.
success
:
// switch (productType) {
switch
(
productType
)
{
// case ProductType.voucher:
case
ProductType
.
voucher
:
// return DirectionalScreen(clickActionType: 'productOwnVoucher', clickActionParam: itemId);
return
DirectionalScreen
.
buildByName
(
// case ProductType.topupMobile:
name:
DirectionalScreenName
.
productOwnVoucher
,
// return DirectionalScreen(clickActionType: 'mobileTopup', clickActionParam: itemId);
clickActionParam:
itemId
,
// case ProductType.typeCard:
);
// return DirectionalScreen(clickActionType: 'familyMedonDetailCard', clickActionParam: itemId);
case
ProductType
.
topupMobile
:
// case ProductType.vnTra:
return
DirectionalScreen
.
buildByName
(
name:
DirectionalScreenName
.
mobileTopup
,);
// return DirectionalScreen(clickActionType: 'detailTrafficService', clickActionParam: itemId);
case
ProductType
.
typeCard
:
// default:
return
DirectionalScreen
.
buildByName
(
// return null;
name:
DirectionalScreenName
.
familyMedonDetailCard
,
// }
clickActionParam:
itemId
,
// case TransactionStatusOrder.failed:
);
// return DirectionalScreen(clickActionType: 'customerSupport');
case
ProductType
.
vnTra
:
// default:
return
DirectionalScreen
.
buildByName
(
// return DirectionalScreen(clickActionType: 'home');
name:
DirectionalScreenName
.
detailTrafficService
,
// }
clickActionParam:
itemId
,
// }
);
default
:
return
null
;
}
case
TransactionStatusOrder
.
failed
:
return
DirectionalScreen
.
buildByName
(
name:
DirectionalScreenName
.
customerSupport
,);
default
:
return
null
;
}
}
}
}
class
ProductInfoModel
{
class
ProductInfoModel
{
...
...
lib/screen/transaction/history/transaction_history_response_model.dart
0 → 100644
View file @
efb4662c
import
'package:mypoint_flutter_app/screen/transaction/history/transaction_history_model.dart'
;
class
TransactionHistoryResponse
{
List
<
TransactionHistoryModel
>?
items
;
TransactionHistorySummaryModel
?
summary
;
TransactionHistoryResponse
({
this
.
items
,
this
.
summary
});
factory
TransactionHistoryResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
TransactionHistoryResponse
(
items:
(
json
[
'items'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
TransactionHistoryModel
.
fromJson
(
e
))
.
toList
(),
summary:
json
[
'summary'
]
!=
null
?
TransactionHistorySummaryModel
.
fromJson
(
json
[
'summary'
])
:
null
,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'items'
:
items
?.
map
((
e
)
=>
e
.
toJson
()).
toList
(),
'summary'
:
summary
?.
toJson
(),
};
}
}
class
TransactionHistorySummaryModel
{
String
?
total
;
String
?
totalPayCash
;
String
?
totalPayPoint
;
int
?
totalOrder
;
TransactionHistorySummaryModel
({
this
.
total
,
this
.
totalPayCash
,
this
.
totalPayPoint
,
this
.
totalOrder
,
});
factory
TransactionHistorySummaryModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
TransactionHistorySummaryModel
(
total:
json
[
'total'
],
totalPayCash:
json
[
'total_pay_cash'
],
totalPayPoint:
json
[
'total_pay_point'
],
totalOrder:
json
[
'total_order'
],
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'total'
:
total
,
'total_pay_cash'
:
totalPayCash
,
'total_pay_point'
:
totalPayPoint
,
'total_order'
:
totalOrder
,
};
}
}
lib/screen/transaction/transaction_detail_viewmodel.dart
View file @
efb4662c
...
@@ -95,8 +95,6 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
...
@@ -95,8 +95,6 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
showAlertBack:
true
,
showAlertBack:
true
,
callback:
(
result
)
{
callback:
(
result
)
{
if
(
result
==
PaymentProcess
.
success
)
{
if
(
result
==
PaymentProcess
.
success
)
{
print
(
"PaymentProcess.success"
);
print
(
data
?.
id
??
""
);
Get
.
offNamed
(
Get
.
offNamed
(
transactionHistoryDetailScreen
,
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
data
?.
id
??
""
,
"canBack"
:
true
},
arguments:
{
"orderId"
:
data
?.
id
??
""
,
"canBack"
:
true
},
...
...
lib/screen/transaction/transactions_history_screen.dart
0 → 100644
View file @
efb4662c
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:month_picker_dialog/month_picker_dialog.dart'
;
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/screen/transaction/transactions_history_viewmodel.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'history/transaction_history_model.dart'
;
import
'history/transaction_history_response_model.dart'
;
class
TransactionHistoryScreen
extends
StatefulWidget
{
const
TransactionHistoryScreen
({
super
.
key
});
@override
State
<
TransactionHistoryScreen
>
createState
()
=>
_TransactionHistoryScreenState
();
}
class
_TransactionHistoryScreenState
extends
State
<
TransactionHistoryScreen
>
{
final
TransactionsHistoryViewModel
_viewModel
=
Get
.
put
(
TransactionsHistoryViewModel
());
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Lịch sử giao dịch'
),
body:
Obx
(()
{
final
summary
=
_viewModel
.
historyResponse
.
value
?.
summary
;
final
items
=
_viewModel
.
historyResponse
.
value
?.
items
??
[];
return
Column
(
children:
[
_buildCategoryTabs
(),
_buildSummaryBox
(
summary
),
const
SizedBox
(
height:
8
),
if
(
items
.
isEmpty
)
Expanded
(
child:
Center
(
child:
EmptyWidget
(
size:
Size
(
200
,
200
)))),
Expanded
(
child:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
items
.
length
,
itemBuilder:
(
context
,
index
)
{
final
item
=
items
[
index
];
return
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
item
.
id
??
""
},
);
},
child:
_buildTransactionItem
(
item
));
},
),
),
],
);
}),
);
}
Widget
_buildCategoryTabs
()
{
return
SizedBox
(
height:
48
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
_viewModel
.
categories
.
value
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
8
),
itemBuilder:
(
context
,
index
)
{
final
category
=
_viewModel
.
categories
.
value
[
index
];
final
isSelected
=
category
.
code
==
_viewModel
.
categorySelected
?.
code
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
_viewModel
.
categorySelected
=
category
;
_viewModel
.
getTransactionHistoryResponse
();
});
},
child:
Chip
(
label:
Text
(
category
.
name
??
''
,
style:
TextStyle
(
color:
isSelected
?
BaseColor
.
primary500
:
Colors
.
black54
),
),
backgroundColor:
isSelected
?
Colors
.
red
.
shade50
:
Colors
.
grey
.
shade100
,
),
);
},
),
);
}
Widget
_buildSummaryBox
(
TransactionHistorySummaryModel
?
summary
)
{
return
Column
(
children:
[
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Row
(
children:
[
Text
(
'Tháng
${_viewModel.date.toFormattedString(format: 'MM/yyyy')}
'
,
style:
TextStyle
(
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
width:
4
),
Text
(
'(
${_viewModel.historyResponse.value?.items?.length ?? 0}
giao dịch)'
,
style:
const
TextStyle
(
color:
Colors
.
black54
,
fontSize:
14
),
),
Spacer
(),
GestureDetector
(
onTap:
()
async
{
// final picked = await showDatePicker(
// context: context,
// initialDatePickerMode: DatePickerMode.year,
// initialDate: _viewModel.date,
// firstDate: DateTime(1900),
// lastDate: DateTime(2100),
// );
// if (picked != null) {
// setState(() {
// _viewModel.date = picked;
// _viewModel.getTransactionHistoryResponse();
// });
// }
showMonthPicker
(
context:
context
,
initialDate:
_viewModel
.
date
,
lastDate:
DateTime
.
now
()).
then
((
date
,
)
{
if
(
date
!=
null
)
{
setState
(()
{
_viewModel
.
date
=
date
;
_viewModel
.
getTransactionHistoryResponse
();
});
}
});
},
child:
Container
(
padding:
const
EdgeInsets
.
all
(
6
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
black26
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Row
(
children:
[
Icon
(
Icons
.
calendar_today_outlined
,
size:
16
),
const
SizedBox
(
width:
2
),
Text
(
"Tháng"
),
const
SizedBox
(
width:
2
),
Icon
(
Icons
.
keyboard_arrow_down
,
size:
20
),
],
),
),
),
],
),
),
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
BaseColor
.
primary200
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
(
summary
?.
total
??
''
).
isEmpty
?
"0"
:
summary
?.
total
??
''
,
style:
const
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
height:
4
),
Row
(
children:
[
Text
(
'Giao dịch bằng tiền'
,
style:
TextStyle
(
color:
Colors
.
black87
)),
const
Spacer
(),
Text
(
summary
?.
totalPayCash
??
"0"
,
style:
const
TextStyle
(
color:
Colors
.
black87
,
fontSize:
14
)),
],
),
Row
(
children:
[
Text
(
'Giao dịch bằng điểm'
,
style:
TextStyle
(
color:
Colors
.
black87
)),
const
Spacer
(),
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
16
,
height:
16
),
const
SizedBox
(
width:
2
),
Text
(
summary
?.
totalPayPoint
??
"0"
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
14
)),
],
),
],
),
),
],
);
}
Widget
_buildTransactionItem
(
TransactionHistoryModel
item
)
{
return
Column
(
children:
[
ListTile
(
leading:
loadNetworkImage
(
url:
item
.
logo
??
''
,
width:
40
,
height:
40
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/ic_membership_voucher.png'
,
),
title:
Text
(
item
.
name
??
'Thanh toán mua ưu đãi'
),
subtitle:
Text
(
item
.
createdAt
??
''
,
style:
const
TextStyle
(
fontSize:
13
,
color:
Colors
.
black54
)),
trailing:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
end
,
children:
[
if
((
item
.
payCash
??
''
).
isNotEmpty
)
Text
(
item
.
payCash
??
'0đ'
,
style:
TextStyle
(
fontSize:
14
)),
if
((
item
.
payPoint
??
''
).
isNotEmpty
)
Row
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
16
,
height:
16
),
const
SizedBox
(
width:
2
),
Text
(
item
.
payPoint
??
"0"
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
14
)),
],
),
],
),
),
Divider
(
height:
1
,
color:
Colors
.
grey
.
shade100
,
indent:
20
,
endIndent:
20
),
],
);
}
}
lib/screen/transaction/transactions_history_viewmodel.dart
0 → 100644
View file @
efb4662c
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'history/transaction_category_model.dart'
;
import
'history/transaction_history_response_model.dart'
;
class
TransactionsHistoryViewModel
extends
RestfulApiViewModel
{
var
categories
=
RxList
<
TransactionCategoryModel
>();
var
historyResponse
=
Rxn
<
TransactionHistoryResponse
>();
void
Function
(
String
message
)?
onShowAlertError
;
TransactionCategoryModel
?
categorySelected
;
DateTime
date
=
DateTime
.
now
();
bool
_isLoading
=
false
;
@override
onInit
()
{
super
.
onInit
();
_getCategories
();
}
Future
<
void
>
_getCategories
()
async
{
showLoading
();
try
{
final
response
=
await
client
.
getTransactionHistoryCategories
();
categories
.
value
=
response
.
data
??
[];
categorySelected
=
categories
.
isNotEmpty
?
categories
.
first
:
null
;
hideLoading
();
getTransactionHistoryResponse
();
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
Future
<
void
>
getTransactionHistoryResponse
()
async
{
if
(
_isLoading
)
return
;
final
body
=
{
'category_code'
:
categorySelected
?.
code
??
''
,
'date'
:
date
.
toFormattedString
(
format:
'yyyy-MM'
),
'limit'
:
10000
,
'offset'
:
0
,
};
_isLoading
=
true
;
showLoading
();
try
{
final
response
=
await
client
.
getTransactionHistoryResponse
(
body
);
final
data
=
response
.
data
;
historyResponse
.
value
=
data
;
_isLoading
=
false
;
hideLoading
();
}
catch
(
error
)
{
_isLoading
=
false
;
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
}
lib/screen/voucher/detail/voucher_detail_viewmodel.dart
View file @
efb4662c
...
@@ -113,11 +113,13 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
...
@@ -113,11 +113,13 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
)
)
.
then
((
value
)
{
.
then
((
value
)
{
hideLoading
();
hideLoading
();
if
(!
value
.
isSuccess
)
{
if
(
value
.
isSuccess
&&
(
value
.
data
?.
id
??
""
).
isNotEmpty
)
{
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
Get
.
offNamed
(
transactionHistoryDetailScreen
,
arguments:
{
"orderId"
:
value
.
data
?.
id
??
""
,
"canBack"
:
false
},
);
}
else
{
}
else
{
// Success -> go to transaction detail screen
onShowAlertError
?.
call
(
value
.
errorMessage
??
Constants
.
commonError
);
onShowAlertError
?.
call
(
"Redeem success -> go to transaction detail screen"
);
}
}
});
});
}
}
...
...
lib/screen/voucher/sub_widget/voucher_action_menu.dart
View file @
efb4662c
...
@@ -21,8 +21,8 @@ class VoucherActionMenu extends StatelessWidget {
...
@@ -21,8 +21,8 @@ class VoucherActionMenu extends StatelessWidget {
children:
const
[
children:
const
[
_ActionItem
(
icon:
"assets/images/ic_topup.png"
,
label:
'Nạp tiền
\n
diện thoại'
,
type:
DirectionalScreenName
.
topup
,),
_ActionItem
(
icon:
"assets/images/ic_topup.png"
,
label:
'Nạp tiền
\n
diện thoại'
,
type:
DirectionalScreenName
.
topup
,),
_ActionItem
(
icon:
"assets/images/ic_card_code.png"
,
label:
'Đổi mã
\n
thẻ nạp'
,
type:
DirectionalScreenName
.
productMobileCard
,),
_ActionItem
(
icon:
"assets/images/ic_card_code.png"
,
label:
'Đổi mã
\n
thẻ nạp'
,
type:
DirectionalScreenName
.
productMobileCard
,),
_ActionItem
(
icon:
"assets/images/ic_sim_service.png"
,
label:
'Gói cước
\n
nhà mạng'
,
type:
DirectionalScreenName
.
carrierPackag
e
,),
_ActionItem
(
icon:
"assets/images/ic_sim_service.png"
,
label:
'Gói cước
\n
nhà mạng'
,
type:
DirectionalScreenName
.
simServic
e
,),
_ActionItem
(
icon:
"assets/images/ic_topup_data.png"
,
label:
'Ưu đãi
\n
Data'
,
type:
DirectionalScreenName
.
simService
,),
_ActionItem
(
icon:
"assets/images/ic_topup_data.png"
,
label:
'Ưu đãi
\n
Data'
,
type:
DirectionalScreenName
.
mobileTopupData
,),
],
],
),
),
);
);
...
@@ -43,10 +43,8 @@ class _ActionItem extends StatelessWidget {
...
@@ -43,10 +43,8 @@ class _ActionItem extends StatelessWidget {
return
GestureDetector
(
return
GestureDetector
(
onTap:
()
{
onTap:
()
{
final
param
=
type
==
DirectionalScreenName
.
carrierPackage
?
"https://mypoint.uudaigoicuoc.com/"
:
null
;
DirectionalScreen
?
screen
=
DirectionalScreen
.
build
(
DirectionalScreen
?
screen
=
DirectionalScreen
.
build
(
clickActionType:
type
.
rawValue
,
clickActionType:
type
.
rawValue
,
clickActionParam:
param
,
);
);
screen
?.
begin
();
screen
?.
begin
();
},
},
...
...
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