Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
fa01087d
Commit
fa01087d
authored
Jul 04, 2025
by
DatHV
Browse files
update brand, detail..
parent
c8abf95b
Changes
108
Show whitespace changes
Inline
Side-by-side
lib/screen/
shopping
/model/cashback_overview_model.dart
→
lib/screen/
affiliate
/model/cashback_overview_model.dart
View file @
fa01087d
File moved
lib/screen/
shopping
/model/cashback_overview_model.g.dart
→
lib/screen/
affiliate
/model/cashback_overview_model.g.dart
View file @
fa01087d
File moved
lib/screen/
shopping
/sub_widget/build_affiliate_brand.dart
→
lib/screen/
affiliate
/sub_widget/build_affiliate_brand.dart
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'../../../resouce/base_color.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../model/affiliate_brand_model.dart'
;
class
AffiliateBrand
extends
StatelessWidget
{
final
List
<
AffiliateBrandModel
>
brands
;
const
AffiliateBrand
({
super
.
key
,
required
this
.
brands
});
List
<
AffiliateBrandModel
>
get
displayBrands
{
return
brands
.
take
(
6
).
toList
();
}
@override
Widget
build
(
BuildContext
context
)
{
if
(
b
rands
.
isEmpty
)
{
if
(
displayB
rands
.
isEmpty
)
{
return
const
SizedBox
.
shrink
();
}
final
space
=
8.0
;
final
width
=
(
MediaQuery
.
of
(
context
).
size
.
width
-
space
*
2
-
32
)
/
3
;
final
width
=
(
MediaQuery
.
of
(
context
).
size
.
width
-
space
*
2
-
32
)
/
3
;
return
Column
(
children:
[
const
SizedBox
(
height:
24
),
...
...
@@ -21,8 +28,15 @@ class AffiliateBrand extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
"Thương Hiệu Hoàn Điểm"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
20
)),
Text
(
"Xem tất cả"
,
style:
TextStyle
(
color:
BaseColor
.
primary400
,
fontSize:
14
,
fontWeight:
FontWeight
.
w600
)),
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
affiliateBrandListScreen
,
arguments:
{
"brands"
:
brands
});
},
child:
Text
(
"Xem tất cả"
,
style:
TextStyle
(
color:
BaseColor
.
primary400
,
fontSize:
14
,
fontWeight:
FontWeight
.
w600
),
),
),
],
),
const
SizedBox
(
height:
12
),
...
...
@@ -30,38 +44,37 @@ class AffiliateBrand extends StatelessWidget {
padding:
EdgeInsets
.
zero
,
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
b
rands
.
length
,
itemCount:
displayB
rands
.
length
,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
3
,
childAspectRatio:
width
/
(
width
+
30
),
childAspectRatio:
width
/
(
width
+
30
),
crossAxisSpacing:
8
,
mainAxisSpacing:
8
,
),
itemBuilder:
(
context
,
index
)
{
final
brand
=
b
rands
[
index
];
return
_
buildAffiliateBrandItem
(
brand
);
final
brand
=
displayB
rands
[
index
];
return
buildAffiliateBrandItem
(
brand
);
},
),
],
);
}
}
Widget
_
buildAffiliateBrandItem
(
AffiliateBrandModel
brand
)
{
Widget
buildAffiliateBrandItem
(
AffiliateBrandModel
brand
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2
;
return
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
return
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
affiliateBrandDetailScreen
,
arguments:
{
"brandId"
:
brand
.
brandId
});
},
child:
Container
(
padding:
const
EdgeInsets
.
all
(
6
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
5
,
offset:
const
Offset
(
0
,
2
),
)
],
borderRadius:
BorderRadius
.
circular
(
8
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
5
,
offset:
const
Offset
(
0
,
2
))],
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
...
...
@@ -90,24 +103,18 @@ class AffiliateBrand extends StatelessWidget {
text:
TextSpan
(
style:
const
TextStyle
(
fontSize:
12
),
children:
[
const
TextSpan
(
text:
"Hoàn đến: "
,
style:
TextStyle
(
color:
Colors
.
grey
),
),
const
TextSpan
(
text:
"Hoàn đến: "
,
style:
TextStyle
(
color:
Colors
.
grey
)),
TextSpan
(
text:
"
${(brand.commision ?? '').toNum()?.toString() ?? ''}
%"
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
,
),
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
),
),
],
),
),
],
),
),
);
},
);
}
}
lib/screen/
shopping
/sub_widget/build_affiliate_category.dart
→
lib/screen/
affiliate
/sub_widget/build_affiliate_category.dart
View file @
fa01087d
...
...
@@ -4,7 +4,8 @@ import '../model/affiliate_category_model.dart';
class
AffiliateCategory
extends
StatelessWidget
{
final
List
<
AffiliateCategoryModel
>
categories
;
const
AffiliateCategory
({
super
.
key
,
required
this
.
categories
});
final
void
Function
(
AffiliateCategoryModel
)?
onTap
;
const
AffiliateCategory
({
super
.
key
,
required
this
.
categories
,
this
.
onTap
});
@override
Widget
build
(
BuildContext
context
)
{
...
...
@@ -38,7 +39,11 @@ class AffiliateCategory extends StatelessWidget {
}
Widget
_buildAffiliateCategoryItem
(
AffiliateCategoryModel
category
)
{
return
LayoutBuilder
(
return
GestureDetector
(
onTap:
()
{
onTap
?.
call
(
category
);
},
child:
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2
;
return
Container
(
...
...
@@ -49,7 +54,7 @@ class AffiliateCategory extends StatelessWidget {
Image
.
asset
(
'assets/images/cashback/
${category.icon}
.png'
,
width:
imageWidth
,
height:
imageWidth
,),
const
SizedBox
(
height:
4
),
Text
(
category
.
name
,
category
.
name
??
''
,
textAlign:
TextAlign
.
center
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
...
...
@@ -59,6 +64,7 @@ class AffiliateCategory extends StatelessWidget {
),
);
},
),
);
}
}
lib/screen/
shopping
/sub_widget/build_affiliate_product_topsale.dart
→
lib/screen/
affiliate
/sub_widget/build_affiliate_product_topsale.dart
View file @
fa01087d
...
...
@@ -52,7 +52,12 @@ class AffiliateProductTopSale extends StatelessWidget {
final
imageUrl
=
product
.
thumnailLink
??
''
;
final
title
=
product
.
productName
??
''
;
return
Container
(
return
GestureDetector
(
onTap:
()
{
print
(
"name
${product.productLink}
"
);
product
.
direcionalScreen
?.
begin
();
},
child:
Container
(
width:
160
,
padding:
const
EdgeInsets
.
all
(
8
),
decoration:
BoxDecoration
(
...
...
@@ -107,6 +112,7 @@ class AffiliateProductTopSale extends StatelessWidget {
],
),
],
)
),
);
}
...
...
lib/screen/affiliate_brand_detail/affiliate_brand_detail_screen.dart
0 → 100644
View file @
fa01087d
import
'dart:math'
;
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'../../../base/base_screen.dart'
;
import
'../../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/image_loader.dart'
;
import
'../../widgets/measure_size.dart'
;
import
'affiliate_brand_detail_tip_box.dart'
;
import
'affiliate_brand_detail_viewmodel.dart'
;
import
'package:get/get.dart'
;
import
'affiliate_brand_detail_tag_list.dart'
;
class
AffiliateBrandDetailScreen
extends
BaseScreen
{
const
AffiliateBrandDetailScreen
({
super
.
key
});
@override
_AffiliateBrandDetailScreenState
createState
()
=>
_AffiliateBrandDetailScreenState
();
}
class
_AffiliateBrandDetailScreenState
extends
BaseState
<
AffiliateBrandDetailScreen
>
with
BasicState
{
late
final
AffiliateBrandDetailViewModel
_viewModel
;
double
_infoHeight
=
0
;
@override
void
initState
()
{
super
.
initState
();
String
?
brandId
;
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
brandId
=
args
[
'brandId'
];
}
if
((
brandId
??
''
).
isEmpty
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
Get
.
back
();
});
return
;
}
_viewModel
=
Get
.
put
(
AffiliateBrandDetailViewModel
(
brandId
??
''
));
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
}
@override
Widget
createBody
()
{
return
Scaffold
(
backgroundColor:
Colors
.
grey
.
shade100
,
body:
Obx
(()
{
double
heightStepTutorial
=
100
;
var
categoriesTag
=
_viewModel
.
brandDetailData
.
value
?.
categories
??
[];
var
shoulds
=
_viewModel
.
brandDetailData
.
value
?.
contentShould
??
[];
var
shouldnts
=
_viewModel
.
brandDetailData
.
value
?.
contentShouldnt
??
[];
var
notes
=
_viewModel
.
brandDetailData
.
value
?.
contentNote
??
[];
var
others
=
_viewModel
.
brandDetailData
.
value
?.
contentOther
??
[];
return
Stack
(
children:
[
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildHeaderWithInfo
(),
SizedBox
(
height:
max
(
_infoHeight
-
54
,
0
)),
_buildCardGuide
(),
Container
(
color:
Colors
.
white
,
margin:
EdgeInsets
.
only
(
top:
16
,
bottom:
16
),
padding:
EdgeInsets
.
only
(
top:
16
,
bottom:
16
),
child:
SizedBox
(
height:
heightStepTutorial
,
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
children:
[
const
SizedBox
(
width:
12
),
_buildStepCard
(
heightStepTutorial
,
"assets/images/banner_tutorial_refund_point_step1.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
heightStepTutorial
,
"assets/images/banner_tutorial_refund_point_step2.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
heightStepTutorial
,
"assets/images/banner_tutorial_refund_point_step3.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
heightStepTutorial
,
"assets/images/banner_tutorial_refund_point_step4.png"
),
const
SizedBox
(
width:
12
),
],
),
),
),
if
(
categoriesTag
.
isNotEmpty
)
AffiliateTagListScrollable
(
items:
categoriesTag
),
if
(
shoulds
.
isNotEmpty
)
AffiliateBrandDetailTipBox
(
type:
AffiliateTipBoxType
.
should
,
tips:
shoulds
),
if
(
shouldnts
.
isNotEmpty
)
AffiliateBrandDetailTipBox
(
type:
AffiliateTipBoxType
.
shouldnt
,
tips:
shouldnts
),
if
(
notes
.
isNotEmpty
)
AffiliateBrandDetailTipBox
(
type:
AffiliateTipBoxType
.
note
,
tips:
notes
),
if
(
others
.
isNotEmpty
)
AffiliateBrandDetailTipBox
(
type:
AffiliateTipBoxType
.
other
,
tips:
others
),
],
),
),
SafeArea
(
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
12
),
child:
CustomBackButton
())),
],
);
}),
bottomNavigationBar:
_buildButton
(),
);
}
Widget
_buildButton
()
{
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
.
brandDetailData
.
value
?.
directionalScreen
?.
begin
();
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
)),
minimumSize:
const
Size
.
fromHeight
(
48
),
),
child:
const
Text
(
"Mua ngay"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
),
),
);
}
Widget
_buildHeaderWithInfo
()
{
final
double
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
double
imageHeight
=
screenWidth
/
(
16
/
9
);
return
Stack
(
clipBehavior:
Clip
.
none
,
children:
[
loadNetworkImage
(
url:
_viewModel
.
brandDetailData
.
value
?.
background
,
fit:
BoxFit
.
cover
,
height:
imageHeight
,
width:
double
.
infinity
,
placeholderAsset:
'assets/images/bg_header_detail_brand.png'
,
),
Positioned
(
left:
0
,
right:
0
,
child:
MeasureSize
(
onChange:
(
size
)
{
if
(
_infoHeight
!=
size
.
height
)
{
setState
(()
{
_infoHeight
=
size
.
height
;
});
}
},
child:
Transform
.
translate
(
offset:
Offset
(
0
,
imageHeight
-
60
),
child:
_buildCardInfo
()),
),
),
],
);
}
Widget
_buildCardGuide
()
{
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Image
.
asset
(
'assets/images/ic_time_record.png'
,
width:
36
,
height:
36
),
const
SizedBox
(
width:
4
),
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Text
(
'Ghi nhận sau'
,
style:
TextStyle
(
color:
Colors
.
black54
,
fontSize:
12
)),
Text
(
_viewModel
.
brandDetailData
.
value
?.
timeConfirm
??
""
,
style:
TextStyle
(
color:
Colors
.
black87
,
fontSize:
12
,
fontWeight:
FontWeight
.
w700
),
),
],
),
],
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
18
,
right:
18
),
child:
Container
(
width:
1
,
height:
40
,
color:
Colors
.
grey
.
shade300
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
12
),
),
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Image
.
asset
(
'assets/images/ic_time_refund.png'
,
width:
36
,
height:
36
),
const
SizedBox
(
width:
4
),
Column
(
children:
[
const
Text
(
'Hoàn điểm sau'
,
style:
TextStyle
(
color:
Colors
.
black54
,
fontSize:
12
)),
Text
(
_viewModel
.
brandDetailData
.
value
?.
timeCashback
??
""
,
style:
TextStyle
(
color:
Colors
.
black87
,
fontSize:
12
,
fontWeight:
FontWeight
.
w700
),
),
],
),
],
),
],
);
}
Widget
_buildCardInfo
()
{
final
detail
=
_viewModel
.
brandDetailData
.
value
;
if
(
detail
==
null
)
return
SizedBox
();
return
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
padding:
const
EdgeInsets
.
all
(
10
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black12
,
blurRadius:
6
,
offset:
Offset
(
0
,
3
))],
),
child:
Row
(
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
loadNetworkImage
(
url:
detail
.
logo
,
width:
60
,
height:
60
,
placeholderAsset:
"assets/images/ic_logo.png"
,
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
detail
.
brandName
??
''
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
)),
const
SizedBox
(
height:
4
),
RichText
(
text:
TextSpan
(
text:
'Hoàn đến '
,
style:
const
TextStyle
(
color:
Colors
.
black
,
fontSize:
13
),
children:
[
TextSpan
(
text:
detail
.
showCommision
??
''
,
style:
const
TextStyle
(
color:
Colors
.
deepOrangeAccent
,
fontWeight:
FontWeight
.
bold
),
),
],
),
),
],
),
),
const
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
,
size:
16
),
const
SizedBox
(
width:
2
),
Text
(
'
${detail?.quantitySold?.money(CurrencyUnit.noneSpace) ?? '1'}
đơn hàng'
,
style:
TextStyle
(
color:
Colors
.
green
,
fontSize:
13
),
),
],
),
);
}
Widget
_buildStepCard
(
double
height
,
String
imagePath
)
{
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
Image
.
asset
(
imagePath
,
fit:
BoxFit
.
cover
,
width:
height
*
4611
/
2739
,
height:
height
),
);
}
}
lib/screen/affiliate_brand_detail/affiliate_brand_detail_tag_list.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'models/affiliate_brand_detail_model.dart'
;
class
AffiliateTagListScrollable
extends
StatelessWidget
{
final
List
<
AffiliateBrandCategoryModel
>
items
;
const
AffiliateTagListScrollable
({
super
.
key
,
required
this
.
items
});
@override
Widget
build
(
BuildContext
context
)
{
int
lines
=
2
;
if
(
items
.
length
>
20
)
{
lines
=
3
;
}
else
if
(
items
.
length
<
10
)
{
lines
=
1
;
}
const
double
tagHeight
=
36
;
const
double
itemSpacing
=
10
;
final
double
containerHeight
=
lines
*
tagHeight
+
(
lines
-
1
)
*
itemSpacing
;
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Padding
(
padding:
EdgeInsets
.
only
(
left:
16
,
bottom:
8
),
child:
Text
(
"Tỉ lệ hoàn điểm"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
),
),
),
Padding
(
padding:
EdgeInsets
.
only
(
left:
16
,
bottom:
0
),
child:
SizedBox
(
height:
containerHeight
,
child:
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
List
.
generate
(
lines
,
(
lineIndex
)
{
final
lineItems
=
items
.
skip
(
lineIndex
*
(
items
.
length
~/
lines
))
.
take
(
items
.
length
~/
lines
)
.
toList
();
return
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
lineIndex
<
lines
-
1
?
itemSpacing
:
0
),
child:
Row
(
children:
lineItems
.
map
((
item
)
{
return
Container
(
margin:
const
EdgeInsets
.
only
(
right:
itemSpacing
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
6
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
RichText
(
text:
TextSpan
(
text:
'
${item.categoryName}
: '
,
style:
const
TextStyle
(
color:
Colors
.
black87
,
fontSize:
14
),
children:
[
TextSpan
(
text:
item
.
showCommision
,
style:
const
TextStyle
(
color:
Colors
.
deepOrangeAccent
,
fontSize:
14
),
),
],
),
),
);
}).
toList
(),
),
);
}),
),
),
),
),
],
);
}
}
// import 'package:flutter/material.dart';
//
// import 'models/affiliate_brand_detail_model.dart';
//
// class AffiliateTagList extends StatelessWidget {
// final List<AffiliateBrandCategoryModel> items;
//
// const AffiliateTagList({super.key, required this.items});
//
// @override
// Widget build(BuildContext context) {
// int maxLines = 2;
// if (items.length > 20) {
// maxLines = 3;
// } else if (items.length < 10) {
// maxLines = 1;
// }
//
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// const Padding(
// padding: EdgeInsets.all(8.0),
// child: Text(
// "Tỉ lệ hoàn điểm",
// style: TextStyle(fontWeight: FontWeight.bold),
// ),
// ),
// Wrap(
// spacing: 8,
// runSpacing: 8,
// children: items
// .take(maxLines * 3) // optional: limit total number if needed
// .map(
// (item) => Container(
// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
// decoration: BoxDecoration(
// color: Colors.grey.shade100,
// borderRadius: BorderRadius.circular(16),
// ),
// child: RichText(
// text: TextSpan(
// text: '${item.categoryName}: ',
// style: const TextStyle(color: Colors.black87, fontSize: 14),
// children: [
// TextSpan(
// text: item.commision ?? '',
// style: const TextStyle(color: Colors.orange, fontSize: 14),
// ),
// ],
// ),
// ),
// ),
// )
// .toList(),
// ),
// ],
// );
// }
// }
lib/screen/affiliate_brand_detail/affiliate_brand_detail_tip_box.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
enum
AffiliateTipBoxType
{
should
,
shouldnt
,
note
,
other
;
}
extension
AffiliateTipBoxTypeExt
on
AffiliateTipBoxType
{
String
get
title
{
switch
(
this
)
{
case
AffiliateTipBoxType
.
should
:
return
"NÊN"
;
case
AffiliateTipBoxType
.
shouldnt
:
return
"KHÔNG NÊN"
;
case
AffiliateTipBoxType
.
note
:
return
"Ghi chú"
;
case
AffiliateTipBoxType
.
other
:
return
"Thông tin khác"
;
}
}
Color
get
backgroundColor
{
switch
(
this
)
{
case
AffiliateTipBoxType
.
should
:
return
Colors
.
green
.
shade100
;
case
AffiliateTipBoxType
.
shouldnt
:
return
Colors
.
red
.
shade50
;
case
AffiliateTipBoxType
.
note
:
return
Colors
.
white
;
case
AffiliateTipBoxType
.
other
:
return
Colors
.
white
;
}
}
Color
get
textColor
{
switch
(
this
)
{
case
AffiliateTipBoxType
.
should
:
return
Colors
.
green
.
shade600
;
case
AffiliateTipBoxType
.
shouldnt
:
return
Colors
.
red
.
shade800
;
case
AffiliateTipBoxType
.
note
:
return
Colors
.
black87
;
case
AffiliateTipBoxType
.
other
:
return
Colors
.
black87
;
}
}
IconData
get
icon
{
switch
(
this
)
{
case
AffiliateTipBoxType
.
should
:
return
Icons
.
check_circle
;
case
AffiliateTipBoxType
.
shouldnt
:
return
Icons
.
cancel
;
case
AffiliateTipBoxType
.
note
:
return
Icons
.
info
;
case
AffiliateTipBoxType
.
other
:
return
Icons
.
lightbulb
;
}
}
Color
get
iconColor
{
switch
(
this
)
{
case
AffiliateTipBoxType
.
should
:
return
Colors
.
green
;
case
AffiliateTipBoxType
.
shouldnt
:
return
Colors
.
red
;
case
AffiliateTipBoxType
.
note
:
return
Colors
.
grey
;
case
AffiliateTipBoxType
.
other
:
return
Colors
.
blue
;
}
}
}
class
AffiliateBrandDetailTipBox
extends
StatelessWidget
{
final
AffiliateTipBoxType
type
;
final
List
<
String
>
tips
;
const
AffiliateBrandDetailTipBox
({
super
.
key
,
required
this
.
type
,
required
this
.
tips
});
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
margin:
const
EdgeInsets
.
only
(
left:
16
,
right:
16
,
bottom:
16
),
//all(16),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
type
.
backgroundColor
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
type
.
title
,
style:
TextStyle
(
color:
type
.
textColor
,
fontWeight:
FontWeight
.
bold
,
fontSize:
16
,
),
),
const
SizedBox
(
height:
8
),
...
tips
.
map
((
tip
)
=>
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
4
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Icon
(
type
.
icon
,
color:
type
.
iconColor
,
size:
20
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
tip
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
),
),
),
],
),
)),
],
),
);
}
}
lib/screen/affiliate_brand_detail/affiliate_brand_detail_viewmodel.dart
0 → 100644
View file @
fa01087d
import
'package:get/get.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
'models/affiliate_brand_detail_model.dart'
;
class
AffiliateBrandDetailViewModel
extends
RestfulApiViewModel
{
String
brandId
;
AffiliateBrandDetailViewModel
(
this
.
brandId
);
void
Function
(
String
message
)?
onShowAlertError
;
var
isLoading
=
false
.
obs
;
var
brandDetailData
=
Rxn
<
AffiliateBrandDetailModel
>();
@override
void
onInit
()
{
super
.
onInit
();
_getAffiliateBrandDetail
();
}
Future
<
void
>
_getAffiliateBrandDetail
()
async
{
showLoading
();
if
(
isLoading
.
value
)
return
;
try
{
isLoading
.
value
=
true
;
final
response
=
await
client
.
getAffiliateBrandDetail
(
brandId
);
hideLoading
();
if
(
response
.
isSuccess
)
{
brandDetailData
.
value
=
response
.
data
;
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
showLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
finally
{
isLoading
.
value
=
false
;
}
}
}
lib/screen/affiliate_brand_detail/affiliate_brand_list_screen.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../affiliate/model/affiliate_brand_model.dart'
;
import
'../affiliate/sub_widget/build_affiliate_brand.dart'
;
class
AffiliateBrandListScreen
extends
StatefulWidget
{
const
AffiliateBrandListScreen
({
super
.
key
});
@override
_AffiliateBrandListScreenState
createState
()
=>
_AffiliateBrandListScreenState
();
}
class
_AffiliateBrandListScreenState
extends
State
<
AffiliateBrandListScreen
>
{
List
<
AffiliateBrandModel
>
_brands
=
[];
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
_brands
=
args
[
'brands'
];
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
space
=
8.0
;
final
width
=
(
MediaQuery
.
of
(
context
).
size
.
width
-
space
*
2
-
32
)
/
3
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Thương hiệu hoàn điểm"
),
backgroundColor:
Colors
.
grey
.
shade100
,
body:
SingleChildScrollView
(
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
12.0
),
child:
GridView
.
builder
(
padding:
EdgeInsets
.
zero
,
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
_brands
.
length
,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
3
,
childAspectRatio:
width
/
(
width
+
30
),
crossAxisSpacing:
8
,
mainAxisSpacing:
8
,
),
itemBuilder:
(
context
,
index
)
{
final
brand
=
_brands
[
index
];
return
buildAffiliateBrandItem
(
brand
);
},
),
),
),
);
}
}
\ No newline at end of file
lib/screen/affiliate_brand_detail/affiliate_category_grid_screen.dart
0 → 100644
View file @
fa01087d
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../affiliate/affiliate_popup_brands.dart'
;
import
'../affiliate/model/affiliate_category_model.dart'
;
import
'affiliate_category_grid_viewmodel.dart'
;
class
AffiliateCategoryGridScreen
extends
StatefulWidget
{
const
AffiliateCategoryGridScreen
({
super
.
key
});
@override
_AffiliateCategoryGridScreenState
createState
()
=>
_AffiliateCategoryGridScreenState
();
}
class
_AffiliateCategoryGridScreenState
extends
State
<
AffiliateCategoryGridScreen
>
{
final
AffiliateCategoryGridViewModel
viewModel
=
Get
.
put
(
AffiliateCategoryGridViewModel
());
List
<
AffiliateCategoryModel
>
_categories
=
[];
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
_categories
=
args
[
'categories'
];
}
viewModel
.
onShowAffiliateBrandPopup
=
(
data
)
{
showAffiliateBrandPopup
(
context
,
data
.
$
1
,
title:
data
.
$
2
);
};
}
@override
Widget
build
(
BuildContext
context
)
{
final
space
=
8.0
;
final
width
=
(
MediaQuery
.
of
(
context
).
size
.
width
-
space
*
5
)
/
4
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Lĩnh vực hoàn điểm"
),
backgroundColor:
Colors
.
white
,
body:
SingleChildScrollView
(
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
GridView
.
count
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
crossAxisCount:
4
,
childAspectRatio:
width
/
(
width
+
30
),
children:
_categories
.
map
((
category
)
=>
_buildAffiliateCategoryItem
(
category
)).
toList
(),
),
),
),
);
}
Widget
_buildAffiliateCategoryItem
(
AffiliateCategoryModel
category
)
{
return
GestureDetector
(
onTap:
()
{
viewModel
.
affiliateBrandGetListBuyCategory
(
category
);
},
child:
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2
;
return
Container
(
padding:
const
EdgeInsets
.
only
(
top:
12
,
bottom:
8
,
left:
4
,
right:
4
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
children:
[
Image
.
asset
(
'assets/images/cashback/
${category.icon}
.png'
,
width:
imageWidth
,
height:
imageWidth
),
const
SizedBox
(
height:
4
),
Text
(
category
.
name
??
''
,
textAlign:
TextAlign
.
center
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
fontWeight:
FontWeight
.
w600
,
fontSize:
14
,
color:
BaseColor
.
second500
),
),
],
),
);
},
),
);
}
}
lib/screen/affiliate_brand_detail/affiliate_category_grid_viewmodel.dart
0 → 100644
View file @
fa01087d
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
'../affiliate/model/affiliate_brand_model.dart'
;
import
'../affiliate/model/affiliate_category_model.dart'
;
class
AffiliateCategoryGridViewModel
extends
RestfulApiViewModel
{
final
RxBool
isLoading
=
false
.
obs
;
void
Function
((
List
<
AffiliateBrandModel
>,
String
)
data
)?
onShowAffiliateBrandPopup
;
affiliateBrandGetListBuyCategory
(
AffiliateCategoryModel
category
)
async
{
showLoading
();
try
{
final
result
=
await
client
.
affiliateBrandGetList
(
categoryCode:
AffiliateCategoryModel
.
codeToJson
(
category
.
code
));
hideLoading
();
final
data
=
result
.
data
??
[];
if
(
result
.
isSuccess
&&
data
.
isNotEmpty
)
{
onShowAffiliateBrandPopup
?.
call
((
data
,
category
.
name
??
''
));
}
}
catch
(
error
)
{
hideLoading
();
print
(
"Error fetching affiliate brands:
$error
"
);
}
}
}
\ No newline at end of file
lib/screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart
0 → 100644
View file @
fa01087d
import
'package:mypoint_flutter_app/directional/directional_action_type.dart'
;
import
'../../../configs/constants.dart'
;
import
'../../../directional/directional_screen.dart'
;
class
AffiliateBrandDetailModel
{
final
String
?
commision
;
final
String
?
direction
;
final
String
?
brandId
;
final
String
?
brandName
;
final
String
?
brandUrl
;
final
String
?
timeConfirm
;
final
String
?
timeCashback
;
final
String
?
logo
;
final
String
?
background
;
final
List
<
String
>?
contentNote
;
final
List
<
String
>?
contentShould
;
final
List
<
String
>?
contentShouldnt
;
final
List
<
String
>?
contentOther
;
final
List
<
AffiliateBrandCategoryModel
>?
categories
;
final
int
?
quantitySold
;
AffiliateBrandDetailModel
({
this
.
commision
,
this
.
direction
,
this
.
brandId
,
this
.
brandName
,
this
.
brandUrl
,
this
.
timeConfirm
,
this
.
timeCashback
,
this
.
logo
,
this
.
background
,
this
.
contentNote
,
this
.
contentShould
,
this
.
contentShouldnt
,
this
.
contentOther
,
this
.
categories
,
this
.
quantitySold
,
});
String
get
showCommision
{
return
'
${double.tryParse(commision ?? '0')?.toStringAsFixed(1) ?? '0.0'}
%'
;
}
String
get
imgBackgroundAsset
{
if
(
background
!=
null
&&
background
!.
isNotEmpty
)
{
return
'assets/images/bg_header_detail_brand_
${background!}
.png'
;
}
return
'assets/images/bg_header_detail_brand_DEFAULT.png'
;
}
DirectionalScreen
?
get
directionalScreen
{
if
(
brandUrl
==
null
||
brandUrl
!.
isEmpty
)
return
null
;
if
(
direction
==
Constants
.
directionInApp
)
{
return
DirectionalScreen
.
buildByName
(
name:
DirectionalScreenName
.
viewDeepLinkInApp
,
clickActionParam:
brandUrl
);
}
return
DirectionalScreen
.
buildByName
(
name:
DirectionalScreenName
.
viewDeepLink
,
clickActionParam:
brandUrl
);
}
factory
AffiliateBrandDetailModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
AffiliateBrandDetailModel
(
commision:
json
[
'commision'
],
direction:
json
[
'direction'
],
brandId:
json
[
'brand_id'
],
brandName:
json
[
'brand_name'
],
brandUrl:
json
[
'brand_url'
],
timeConfirm:
json
[
'time_confirm'
],
timeCashback:
json
[
'time_cashback'
],
logo:
json
[
'logo'
],
background:
json
[
'background'
],
contentNote:
(
json
[
'content_note'
]
as
List
?)?.
cast
<
String
>(),
contentShould:
(
json
[
'content_should'
]
as
List
?)?.
cast
<
String
>(),
contentShouldnt:
(
json
[
'content_shouldnt'
]
as
List
?)?.
cast
<
String
>(),
contentOther:
(
json
[
'content_other'
]
as
List
?)?.
cast
<
String
>(),
categories:
(
json
[
'categories'
]
as
List
?)
?.
map
((
e
)
=>
AffiliateBrandCategoryModel
.
fromJson
(
e
))
.
toList
(),
quantitySold:
json
[
'quantity_sold'
],
);
}
}
class
AffiliateBrandCategoryModel
{
final
String
?
commision
;
final
String
?
categoryName
;
final
String
?
brandUrl
;
AffiliateBrandCategoryModel
({
this
.
commision
,
this
.
categoryName
,
this
.
brandUrl
,
});
String
get
showCommision
{
return
'
${double.tryParse(commision ?? '')?.toStringAsFixed(1) ?? '0.0'}
%'
;
}
factory
AffiliateBrandCategoryModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
AffiliateBrandCategoryModel
(
commision:
json
[
'commision'
]
??
''
,
categoryName:
json
[
'category_name'
]
??
''
,
brandUrl:
json
[
'brand_url'
]
??
''
,
);
}
}
lib/screen/data_network_service/data_network_service_screen.dart
0 → 100644
View file @
fa01087d
import
'package:contacts_service/contacts_service.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/screen/data_network_service/product_network_data_model.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_navigation_bar.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../preference/point/point_manager.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/alert/custom_alert_dialog.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../topup/brand_select_sheet_widget.dart'
;
import
'data_network_service_viewmodel.dart'
;
class
DataNetworkServiceScreen
extends
BaseScreen
{
const
DataNetworkServiceScreen
({
super
.
key
});
@override
State
<
DataNetworkServiceScreen
>
createState
()
=>
_DataNetworkServiceScreenState
();
}
class
_DataNetworkServiceScreenState
extends
BaseState
<
DataNetworkServiceScreen
>
with
BasicState
{
final
DataNetworkServiceViewModel
_viewModel
=
Get
.
put
(
DataNetworkServiceViewModel
());
late
final
TextEditingController
_phoneController
;
@override
void
initState
()
{
super
.
initState
();
_phoneController
=
TextEditingController
(
text:
_viewModel
.
phoneNumber
.
value
);
_viewModel
.
firstLoadNetworkData
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
_viewModel
.
onShowAlertRedeemSuccess
=
(
message
)
{
showAlertError
(
content:
message
);
};
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Ưu đãi Data"
),
body:
Obx
(()
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildHeaderPhone
(),
Container
(
height:
6
,
color:
Colors
.
grey
.
shade200
),
const
Divider
(
height:
8
),
if
(
_viewModel
.
topUpNetworkData
.
isEmpty
)
Expanded
(
child:
EmptyWidget
()),
if
(
_viewModel
.
topUpNetworkData
.
isNotEmpty
)
Expanded
(
child:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
_viewModel
.
topUpNetworkData
.
length
,
itemBuilder:
(
context
,
index
)
{
final
data
=
_viewModel
.
topUpNetworkData
.
value
[
index
];
return
_buildSectionNetworkData
(
data
);
},
),
),
const
Divider
(
height:
1
),
SafeArea
(
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
16
,
right:
16
,
top:
8
,
bottom:
8
),
child:
_buildButton
())),
],
);
}),
);
}
Widget
_buildButton
()
{
return
Obx
(()
{
final
isValidInput
=
(
_viewModel
.
phoneNumber
.
value
.
trim
().
length
>=
10
)
&&
(
_viewModel
.
selectedProduct
.
value
!=
null
);
return
ElevatedButton
(
onPressed:
isValidInput
?
_redeemProductMobileCard
:
null
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isValidInput
?
BaseColor
.
primary500
:
Colors
.
grey
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
)),
minimumSize:
const
Size
.
fromHeight
(
48
),
),
child:
const
Text
(
"Đổi ngay"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
)),
);
});
}
_redeemProductMobileCard
()
{
print
(
"redeem
${UserPointManager().point}
>=
${_viewModel.payPoint}
"
);
final
isValidInput
=
(
_viewModel
.
phoneNumber
.
value
.
trim
().
length
>=
10
)
&&
(
_viewModel
.
selectedProduct
.
value
!=
null
);
if
(!
isValidInput
)
{
showAlertError
(
content:
"Vui lòng chọn gói cước và nhập số điện thoại."
);
return
;
}
if
(!
_viewModel
.
isValidBalance
)
{
showAlertError
(
content:
"Bạn chưa đủ điểm để đổi ưu đãi này, vui lòng tích lũy thêm điểm nhé!"
);
return
;
}
_showAlertConfirmRedeemProduct
();
}
_showAlertConfirmRedeemProduct
()
{
final
dataAlert
=
DataAlertModel
(
title:
"Xác nhận"
,
description:
"Bạn có muốn sử dụng
${_viewModel.payPoint.money(CurrencyUnit.point)}
MyPoint để đổi gói cước này không?"
,
localHeaderImage:
"assets/images/ic_pipi_02.png"
,
buttons:
[
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
{
Get
.
back
();
_viewModel
.
redeemProductMobileCard
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
),
AlertButton
(
text:
"Huỷ"
,
onPressed:
()
=>
Get
.
back
(),
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
second500
),
],
);
showAlert
(
data:
dataAlert
,
direction:
ButtonsDirection
.
row
);
}
Widget
_buildSectionNetworkData
(
TopUpNetworkDataModel
data
)
{
final
packages
=
data
.
products
??
[];
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
child:
Text
(
textAlign:
TextAlign
.
start
,
data
.
groupName
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
),
),
),
GridView
.
builder
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
packages
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
childAspectRatio:
2.8
,
crossAxisSpacing:
8
,
mainAxisSpacing:
12
,
),
itemBuilder:
(
context
,
index
)
{
final
item
=
packages
[
index
];
final
isSelected
=
_viewModel
.
selectedProduct
.
value
?.
id
==
item
.
id
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
_viewModel
.
selectedProduct
.
value
=
item
;
});
},
child:
Container
(
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
isSelected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
Text
(
item
.
name
??
''
,
style:
const
TextStyle
(
color:
BaseColor
.
primary500
,
fontWeight:
FontWeight
.
bold
,
fontSize:
18
,
),
),
const
SizedBox
(
width:
2
),
Text
(
'/
${item.dataDurationApply}
'
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
),
),
],
),
const
SizedBox
(
height:
3
),
Row
(
children:
[
Image
.
asset
(
"assets/images/ic_point.png"
,
width:
18
,
height:
18
,
fit:
BoxFit
.
cover
),
const
SizedBox
(
width:
2
),
Text
(
item
.
payPoint
.
money
(
CurrencyUnit
.
none
),
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
14
,
color:
Colors
.
orange
),
),
],
),
],
),
),
);
},
),
],
);
}
Widget
_buildHeaderPhone
()
{
return
Obx
(()
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
SizedBox
(
height:
8
),
const
Text
(
"Số điện thoại"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
)),
const
SizedBox
(
height:
8
),
Row
(
children:
[
Expanded
(
child:
TextField
(
controller:
_phoneController
,
decoration:
InputDecoration
(
filled:
true
,
fillColor:
Colors
.
grey
.
shade100
,
suffixIcon:
InkWell
(
onTap:
()
=>
pickContact
(
context
),
child:
const
Icon
(
Icons
.
contacts
,
color:
Colors
.
orange
),
),
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
.
none
),
),
keyboardType:
TextInputType
.
phone
,
onChanged:
(
value
)
{
_viewModel
.
phoneNumber
.
value
=
value
;
_viewModel
.
checkMobileNetwork
();
},
),
),
const
SizedBox
(
width:
8
),
GestureDetector
(
onTap:
_viewModel
.
topUpBrands
.
value
.
isEmpty
?
null
:
()
{
showModalBottomSheet
(
context:
context
,
backgroundColor:
Colors
.
transparent
,
isScrollControlled:
true
,
builder:
(
_
)
=>
BrandSelectSheet
(
brands:
_viewModel
.
topUpBrands
.
value
,
selectedBrand:
_viewModel
.
selectedBrand
.
value
,
onSelected:
(
brand
)
{
Navigator
.
pop
(
context
);
if
(
brand
==
null
&&
brand
.
id
!=
_viewModel
.
selectedBrand
.
value
?.
id
)
return
;
_viewModel
.
selectedProduct
.
value
=
null
;
_viewModel
.
selectedBrand
.
value
=
brand
;
_viewModel
.
getTelcoDetail
();
},
),
);
},
child:
Container
(
padding:
const
EdgeInsets
.
all
(
4
),
height:
48
,
width:
64
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
),
child:
loadNetworkImage
(
url:
_viewModel
.
selectedBrand
.
value
?.
logo
,
fit:
BoxFit
.
fitWidth
,
placeholderAsset:
"assets/images/bg_default_169.png"
,
),
),
),
],
),
const
SizedBox
(
height:
16
),
_buildTagHistory
(),
const
SizedBox
(
height:
8
),
],
),
);
});
}
Widget
_buildTagHistory
()
{
final
histories
=
_viewModel
.
histories
;
return
Obx
(()
{
return
SizedBox
(
height:
36
,
child:
Center
(
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
itemCount:
histories
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
8
),
itemBuilder:
(
_
,
index
)
{
final
phone
=
histories
[
index
];
final
myPhone
=
DataPreference
.
instance
.
phone
??
''
;
final
isMyPhone
=
phone
==
myPhone
;
final
selected
=
phone
==
_viewModel
.
phoneNumber
.
value
;
return
GestureDetector
(
onTap:
()
{
setState
(()
{
_viewModel
.
phoneNumber
.
value
=
phone
;
_phoneController
.
text
=
phone
;
_viewModel
.
checkMobileNetwork
();
});
},
child:
Container
(
padding:
EdgeInsets
.
all
(
4
),
decoration:
BoxDecoration
(
color:
selected
?
Colors
.
orange
.
shade50
:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
border:
Border
.
all
(
color:
selected
?
Colors
.
orange
:
Colors
.
grey
.
shade300
),
),
child:
Center
(
child:
Text
(
isMyPhone
?
" Số của tôi "
:
"
$phone
"
,
textAlign:
TextAlign
.
center
,
style:
TextStyle
(
color:
selected
?
Colors
.
orange
:
Colors
.
black54
,
fontSize:
16
,
fontWeight:
selected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
),
),
),
);
},
),
),
);
});
}
Future
<
void
>
pickContact
(
BuildContext
context
)
async
{
try
{
// Gọi sẽ tự động hiện dialog yêu cầu quyền (nếu cần)
final
Contact
?
contact
=
await
ContactsService
.
openDeviceContactPicker
();
if
(
contact
!=
null
&&
contact
.
phones
!=
null
&&
contact
.
phones
!.
isNotEmpty
)
{
String
phone
=
contact
.
phones
!.
first
.
value
??
''
;
phone
=
phone
.
replaceAll
(
RegExp
(
r'[\s\-\(\)]'
),
''
);
_phoneController
.
text
=
phone
;
_viewModel
.
phoneNumber
.
value
=
phone
;
_viewModel
.
checkMobileNetwork
();
}
else
{
ScaffoldMessenger
.
of
(
context
,
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Không tìm thấy số điện thoại hợp lệ"
)));
}
}
catch
(
e
)
{
print
(
"❌ Lỗi khi truy cập danh bạ:
$e
"
);
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Không thể truy cập danh bạ"
)));
}
}
}
lib/screen/data_network_service/data_network_service_viewmodel.dart
0 → 100644
View file @
fa01087d
import
'package:get/get.dart'
;
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'package:mypoint_flutter_app/preference/data_preference.dart'
;
import
'package:mypoint_flutter_app/screen/data_network_service/product_network_data_model.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../configs/constants.dart'
;
import
'../../preference/contact_storage_service.dart'
;
import
'../../preference/point/point_manager.dart'
;
import
'../voucher/models/product_brand_model.dart'
;
import
'../voucher/models/product_model.dart'
;
import
'../voucher/models/product_type.dart'
;
class
DataNetworkServiceViewModel
extends
RestfulApiViewModel
{
var
histories
=
RxList
<
String
>();
final
RxList
<
ProductBrandModel
>
topUpBrands
=
<
ProductBrandModel
>[].
obs
;
final
RxList
<
TopUpNetworkDataModel
>
topUpNetworkData
=
<
TopUpNetworkDataModel
>[].
obs
;
final
Map
<
String
,
List
<
TopUpNetworkDataModel
>>
_allValue
=
{};
var
selectedBrand
=
Rxn
<
ProductBrandModel
>();
var
selectedProduct
=
Rxn
<
ProductNetworkDataModel
>();
var
phoneNumber
=
''
.
obs
;
void
Function
(
String
message
)?
onShowAlertError
;
void
Function
(
String
message
)?
onShowAlertRedeemSuccess
;
int
get
payPoint
{
return
(
int
.
tryParse
(
selectedProduct
.
value
?.
prices
?.
firstOrNull
?.
payPoint
??
"0"
)
??
0
);
}
bool
get
isValidBalance
{
return
UserPointManager
().
point
>=
payPoint
;
}
@override
void
onInit
()
{
super
.
onInit
();
final
myPhone
=
DataPreference
.
instance
.
phone
??
''
;
phoneNumber
.
value
=
myPhone
;
ContactStorageService
().
getUsedContacts
().
then
((
value
)
{
if
(
value
.
isNotEmpty
)
{
histories
.
value
=
value
;
}
else
{
histories
.
value
=
[
myPhone
];
}
});
if
(!
histories
.
contains
(
myPhone
))
{
histories
.
value
.
insert
(
0
,
myPhone
);
ContactStorageService
().
saveUsedContact
(
myPhone
);
}
}
firstLoadNetworkData
()
async
{
showLoading
();
await
getNetworkBrands
();
print
(
"topUpBrands
${topUpBrands.length}
"
);
await
checkMobileNetwork
();
hideLoading
();
}
getNetworkBrands
()
{
client
.
productTopUpBrands
().
then
((
response
)
{
topUpBrands
.
value
=
response
.
data
??
[];
}).
catchError
((
error
)
{
print
(
'Error fetching brands topup:
$error
'
);
});
}
checkMobileNetwork
()
{
client
.
checkMobileNetwork
(
phoneNumber
.
value
).
then
((
response
)
{
final
brandCode
=
response
.
data
?.
brand
??
''
;
final
brand
=
topUpBrands
.
isNotEmpty
?
topUpBrands
.
firstWhere
(
(
brand
)
=>
brand
.
code
==
brandCode
,
orElse:
()
=>
topUpBrands
.
first
,
)
:
null
;
selectedBrand
.
value
=
brand
;
getTelcoDetail
();
}).
catchError
((
error
)
{
final
first
=
topUpBrands
.
value
.
firstOrNull
;
if
(
first
!=
null
)
{
selectedBrand
.
value
=
first
;
}
getTelcoDetail
();
print
(
'Error checking mobile network:
$error
'
);
});
}
Future
<
void
>
getTelcoDetail
({
String
?
selected
})
async
{
final
id
=
selectedBrand
.
value
?.
id
;
final
code
=
selectedBrand
.
value
?.
code
;
if
(
id
==
null
&&
code
==
null
)
return
;
void
makeSelected
(
List
<
TopUpNetworkDataModel
>
data
)
{
bool
didSelect
=
false
;
final
list
=
data
.
expand
((
e
)
=>
e
.
products
??
[])
.
toList
();
if
(
selected
!=
null
&&
selected
.
isNotEmpty
)
{
for
(
var
item
in
list
)
{
final
isMatch
=
item
==
int
.
tryParse
(
selected
);
if
(
isMatch
)
{
selectedProduct
.
value
=
item
;
didSelect
=
true
;
}
}
}
if
(!
didSelect
&&
selectedProduct
.
value
==
null
)
{
selectedProduct
.
value
=
list
.
firstOrNull
;
}
}
// Dùng cache nếu có
if
(
_allValue
.
containsKey
(
code
))
{
final
cached
=
_allValue
[
code
]!;
topUpNetworkData
.
value
=
cached
;
makeSelected
(
cached
);
return
;
}
showLoading
();
try
{
final
result
=
await
client
.
getNetworkProducts
((
id
??
0
).
toString
());
var
data
=
result
.
data
??
[];
data
=
data
.
where
((
e
)
=>
e
.
products
?.
isNotEmpty
==
true
)
.
toList
();
_allValue
[
code
??
""
]
=
data
;
topUpNetworkData
.
value
=
data
;
makeSelected
(
data
);
hideLoading
();
}
catch
(
error
)
{
print
(
"Error fetching all products:
$error
"
);
hideLoading
();
}
}
redeemProductMobileCard
()
async
{
final
id
=
selectedProduct
.
value
?.
id
.
toString
()
??
""
;
showLoading
();
try
{
final
response
=
await
client
.
redeemProductTopUps
(
id
,
phoneNumber
.
value
);
hideLoading
();
if
(!
response
.
isSuccess
)
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
return
;
}
final
mgs
=
(
response
.
errorMessage
??
""
).
isEmpty
?
"Chúc mừng bạn đã đổi Ưu đãi data thành công"
:
(
response
.
errorMessage
??
""
);
onShowAlertRedeemSuccess
?.
call
(
mgs
);
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
error
.
toString
());
return
;
}
}
}
\ No newline at end of file
lib/screen/data_network_service/product_network_data_model.dart
0 → 100644
View file @
fa01087d
import
'package:json_annotation/json_annotation.dart'
;
import
'../mobile_card/models/product_mobile_card_model.dart'
;
part
'product_network_data_model.g.dart'
;
@JsonSerializable
()
class
ProductNetworkDataModel
{
final
int
?
id
;
@JsonKey
(
name:
'product_model_code'
)
final
String
?
productModelCode
;
final
String
?
code
;
final
String
?
name
;
@JsonKey
(
name:
'data_duration_apply'
)
final
String
?
dataDurationApply
;
@JsonKey
(
name:
'description'
)
final
String
?
productDescription
;
@JsonKey
(
name:
'start_time'
)
final
String
?
startTime
;
@JsonKey
(
name:
'end_time'
)
final
String
?
endTime
;
// @JsonKey(name: 'limit_quantity_per_transaction')
// final String? limitQuantityPerTransaction;
final
List
<
MobileCardPriceModel
>?
prices
;
ProductNetworkDataModel
({
this
.
id
,
this
.
productModelCode
,
this
.
code
,
this
.
name
,
this
.
dataDurationApply
,
this
.
productDescription
,
this
.
startTime
,
this
.
endTime
,
// this.limitQuantityPerTransaction,
this
.
prices
,
});
factory
ProductNetworkDataModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$ProductNetworkDataModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$ProductNetworkDataModelToJson
(
this
);
int
get
payPoint
{
if
(
prices
?.
isNotEmpty
!=
true
)
return
0
;
final
point
=
prices
!.
first
.
payPoint
;
return
int
.
tryParse
(
point
??
'0'
)
??
0
;
}
}
@JsonSerializable
()
class
TopUpNetworkDataModel
{
@JsonKey
(
name:
'group_name'
)
final
String
groupName
;
final
List
<
ProductNetworkDataModel
>?
products
;
TopUpNetworkDataModel
({
required
this
.
groupName
,
this
.
products
,
});
factory
TopUpNetworkDataModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$TopUpNetworkDataModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$TopUpNetworkDataModelToJson
(
this
);
}
lib/screen/data_network_service/product_network_data_model.g.dart
0 → 100644
View file @
fa01087d
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'product_network_data_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProductNetworkDataModel
_$ProductNetworkDataModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
ProductNetworkDataModel
(
id:
(
json
[
'id'
]
as
num
?)?.
toInt
(),
productModelCode:
json
[
'product_model_code'
]
as
String
?,
code:
json
[
'code'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
dataDurationApply:
json
[
'data_duration_apply'
]
as
String
?,
productDescription:
json
[
'description'
]
as
String
?,
startTime:
json
[
'start_time'
]
as
String
?,
endTime:
json
[
'end_time'
]
as
String
?,
prices:
(
json
[
'prices'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
MobileCardPriceModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>))
.
toList
(),
);
Map
<
String
,
dynamic
>
_$ProductNetworkDataModelToJson
(
ProductNetworkDataModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'product_model_code'
:
instance
.
productModelCode
,
'code'
:
instance
.
code
,
'name'
:
instance
.
name
,
'data_duration_apply'
:
instance
.
dataDurationApply
,
'description'
:
instance
.
productDescription
,
'start_time'
:
instance
.
startTime
,
'end_time'
:
instance
.
endTime
,
'prices'
:
instance
.
prices
,
};
TopUpNetworkDataModel
_$TopUpNetworkDataModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
TopUpNetworkDataModel
(
groupName:
json
[
'group_name'
]
as
String
,
products:
(
json
[
'products'
]
as
List
<
dynamic
>?)
?.
map
(
(
e
)
=>
ProductNetworkDataModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>),
)
.
toList
(),
);
Map
<
String
,
dynamic
>
_$TopUpNetworkDataModelToJson
(
TopUpNetworkDataModel
instance
,
)
=>
<
String
,
dynamic
>{
'group_name'
:
instance
.
groupName
,
'products'
:
instance
.
products
,
};
lib/screen/game/game_tab_screen.dart
View file @
fa01087d
...
...
@@ -22,10 +22,15 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
final
GlobalKey
_infoKey
=
GlobalKey
();
OverlayEntry
?
_popupEntry
;
bool
_isPopupShown
=
false
;
late
var
_canBackButton
=
false
;
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
_canBackButton
=
args
[
'can_back_button'
]
as
bool
;
}
_viewModel
.
getGames
();
_viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
...
...
@@ -46,7 +51,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Games"
,
showBackButton:
false
,
showBackButton:
_canBackButton
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
rightButtons:
[
CompositedTransformTarget
(
...
...
lib/screen/history_point_cashback/history_point_cashback_screen.dart
0 → 100644
View file @
fa01087d
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/image_loader.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'history_point_cashback_viewmodel.dart'
;
import
'models/history_point_cashback_model.dart'
;
// TODO check api response
class
HistoryPointCashBackScreen
extends
StatefulWidget
{
const
HistoryPointCashBackScreen
({
super
.
key
});
@override
State
<
HistoryPointCashBackScreen
>
createState
()
=>
_HistoryPointCashBackScreenState
();
}
class
_HistoryPointCashBackScreenState
extends
State
<
HistoryPointCashBackScreen
>
{
late
final
HistoryPointCashBackViewModel
_viewModel
;
@override
void
initState
()
{
super
.
initState
();
_viewModel
=
Get
.
put
(
HistoryPointCashBackViewModel
());
}
@override
Widget
build
(
BuildContext
context
)
{
final
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Lịch sử hoàn điểm"
),
body:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceAround
,
children:
[
_buildTab
(
'Chờ xử lý'
,
0
),
_buildTab
(
'Tạm duyệt'
,
1
),
_buildTab
(
'Đã hoàn'
,
2
),
_buildTab
(
'Đã huỷ'
,
3
),
],
),
const
Divider
(
height:
1
),
Obx
(
()
=>
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
16
,
top:
16
),
child:
Row
(
children:
[
Text
(
"Tổng số điểm
${_viewModel.selectedTag.tag.toLowerCase()}
:"
,
style:
const
TextStyle
(
color:
Colors
.
black87
,
fontSize:
17
,
fontWeight:
FontWeight
.
w600
),
),
SizedBox
(
width:
4
),
Image
.
asset
(
"assets/images/ic_point.png"
,
height:
18
),
SizedBox
(
width:
4
),
Text
(
(
_viewModel
.
pointCashBackData
.
value
?.
points
??
0
).
money
(
CurrencyUnit
.
noneSpace
),
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontSize:
18
,
fontWeight:
FontWeight
.
w600
),
),
],
),
),
),
if
(
_viewModel
.
orders
.
isEmpty
)
Expanded
(
child:
EmptyWidget
(
size:
Size
(
screenWidth
/
2
,
screenWidth
/
2
)))
else
Obx
(
()
=>
Expanded
(
child:
RefreshIndicator
(
onRefresh:
()
async
{
_viewModel
.
freshData
(
isRefresh:
true
);
},
child:
ListView
.
builder
(
padding:
const
EdgeInsets
.
all
(
12
),
itemCount:
_viewModel
.
orders
.
length
,
itemBuilder:
(
_
,
index
)
{
if
(
index
>=
_viewModel
.
orders
.
length
)
{
_viewModel
.
freshData
(
isRefresh:
false
);
return
const
Center
(
child:
Padding
(
padding:
EdgeInsets
.
all
(
16
),
child:
CircularProgressIndicator
()),
);
}
final
order
=
_viewModel
.
orders
[
index
];
return
_buildVoucherItem
(
order
);
},
),
),
),
),
],
),
);
}
Widget
_buildTab
(
String
title
,
int
index
)
{
return
GestureDetector
(
onTap:
()
=>
_viewModel
.
selectTab
(
index
),
child:
Obx
(
()
=>
Padding
(
padding:
const
EdgeInsets
.
only
(
top:
8
),
child:
Column
(
children:
[
Text
(
title
,
style:
TextStyle
(
fontSize:
14
,
fontWeight:
_viewModel
.
selectedTabIndex
.
value
==
index
?
FontWeight
.
w600
:
FontWeight
.
w500
,
color:
_viewModel
.
selectedTabIndex
.
value
==
index
?
Colors
.
red
:
Colors
.
black54
,
),
),
const
SizedBox
(
height:
4
),
if
(
_viewModel
.
selectedTabIndex
.
value
==
index
)
Container
(
height:
2
,
width:
60
,
color:
Colors
.
red
),
],
),
),
),
);
}
Widget
_buildVoucherItem
(
HistoryPointCashBackOrderModel
item
)
{
return
Obx
(
()
=>
Container
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
6
,
horizontal:
0
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade200
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
6
),
child:
loadNetworkImage
(
url:
item
.
logo
,
height:
32
,
width:
32
,
placeholderAsset:
"assets/images/ic_logo.png"
,
),
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
"Mã đơn hàng:
${item.code}
"
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
const
SizedBox
(
height:
2
),
Text
(
"HSD:
${item.timeShow}
"
,
style:
TextStyle
(
fontSize:
12
,
color:
Colors
.
black54
)),
],
),
),
Center
(
child:
Text
(
_viewModel
.
selectedTag
.
tag
,
style:
TextStyle
(
color:
_viewModel
.
selectedTag
.
color
,
fontWeight:
FontWeight
.
w600
,
fontSize:
12
),
),
),
],
),
const
SizedBox
(
height:
8
),
Divider
(
height:
1
,
color:
Colors
.
grey
.
shade200
),
const
SizedBox
(
height:
8
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
'Giá trị đơn hàng'
,
style:
TextStyle
(
color:
Colors
.
black54
)),
Text
(
(
item
.
price
??
0
).
money
(
CurrencyUnit
.
VND
),
style:
const
TextStyle
(
color:
Colors
.
red
,
fontWeight:
FontWeight
.
w500
),
),
],
),
const
SizedBox
(
height:
6
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
'Số lượng sản phẩm'
,
style:
TextStyle
(
color:
Colors
.
black54
)),
Text
(
'
${item.productQuantity}
'
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w500
)),
],
),
const
SizedBox
(
height:
6
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
'Điểm
${_viewModel.selectedTag.tag.toLowerCase()}
'
,
style:
const
TextStyle
(
color:
Colors
.
black54
)),
Row
(
children:
[
Image
.
asset
(
"assets/images/ic_point.png"
,
height:
18
),
SizedBox
(
width:
4
),
Text
(
item
.
points
.
money
(
CurrencyUnit
.
noneSpace
),
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
w600
),
),
],
),
],
),
],
),
),
);
}
}
lib/screen/history_point_cashback/history_point_cashback_viewmodel.dart
0 → 100644
View file @
fa01087d
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
'models/history_point_cashback_model.dart'
;
class
HistoryPointCashBackViewModel
extends
RestfulApiViewModel
{
late
List
<
CashBackPointOrderStatus
>
tagStatus
;
CashBackPointOrderStatus
get
selectedTag
{
return
tagStatus
.
safe
(
selectedTabIndex
.
value
)
??
CashBackPointOrderStatus
.
pending
;
}
final
RxInt
selectedTabIndex
=
0
.
obs
;
var
pointCashBackData
=
Rxn
<
HistoryPointCashBackResponse
>();
List
<
HistoryPointCashBackOrderModel
>
get
orders
{
return
pointCashBackData
.
value
?.
orders
??
[];
}
int
_page
=
1
;
@override
void
onInit
()
{
super
.
onInit
();
tagStatus
=
[
CashBackPointOrderStatus
.
pending
,
CashBackPointOrderStatus
.
approved
,
CashBackPointOrderStatus
.
confirmed
,
CashBackPointOrderStatus
.
reject
,
];
freshData
(
isRefresh:
true
);
}
void
freshData
({
bool
isRefresh
=
false
})
{
if
(
isRefresh
)
{
_page
=
1
;
}
else
{
_page
+=
1
;
}
final
body
=
{
"page"
:
_page
,
"size"
:
20
,
"type"
:
selectedTag
.
rawValue
};
client
.
historyPointCashBackRequest
(
body
)
.
then
((
response
)
{
final
result
=
response
.
data
;
if
(
isRefresh
)
{
pointCashBackData
.
value
=
result
;
}
else
{
final
orders
=
result
?.
orders
??
[];
pointCashBackData
.
value
?.
orders
?.
addAll
(
orders
);
pointCashBackData
.
refresh
();
}
})
.
catchError
((
error
)
{
print
(
'Error fetching products:
$error
'
);
});
}
void
selectTab
(
int
index
)
{
selectedTabIndex
.
value
=
index
;
freshData
(
isRefresh:
true
);
}
}
Prev
1
2
3
4
5
6
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment