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
7777aa65
Commit
7777aa65
authored
May 13, 2025
by
DatHV
Browse files
cập nhật cashback affiliate
parent
56e8b038
Changes
44
Hide whitespace changes
Inline
Side-by-side
assets/images/cashback/ic_cashback_tui-vi-nu.png
0 → 100644
View file @
7777aa65
1.32 KB
lib/configs/api_paths.dart
View file @
7777aa65
...
...
@@ -34,4 +34,9 @@ class APIPaths {
static
const
String
getGames
=
"/campaign/api/v3.0/games"
;
static
const
String
verifyOrderProduct
=
"/order/api/v1.0/verify-product"
;
static
const
String
getGameDetail
=
"/campaign/api/v3.0/games/%@/play"
;
static
const
String
affiliateCategoryGetList
=
"/affiliateCategoryGetList/1.0.0"
;
static
const
String
affiliateBrandGetList
=
"/affiliateBrandGetList/1.0.0"
;
static
const
String
affiliateProductTopSale
=
"/affiliateProductTopSale/1.0.0"
;
static
const
String
getCashbackOverview
=
"/cashback/overview"
;
static
const
String
getCashbackStatus
=
"/cashback/status"
;
}
\ No newline at end of file
lib/extensions/num_extension.dart
View file @
7777aa65
...
...
@@ -13,6 +13,18 @@ extension NumExtension on num {
..
minimumFractionDigits
=
0
;
return
formatter
.
format
(
this
);
}
String
formatCompactNumber
()
{
if
(
this
<
1000
)
{
return
toString
();
}
else
if
(
this
<
1000000
)
{
return
'
${(this / 1000).toStringAsFixed(1)}
K'
;
}
else
if
(
this
<
1000000000
)
{
return
'
${(this / 1000000).toStringAsFixed(1)}
M'
;
}
else
{
return
'
${(this / 1000000000).toStringAsFixed(1)}
B'
;
}
}
}
enum
CurrencyUnit
{
...
...
lib/extensions/string_extension.dart
View file @
7777aa65
...
...
@@ -68,4 +68,16 @@ extension HexColorExtension on String {
}
return
null
;
}
}
extension
ParseInt
on
String
{
num
?
toNum
()
{
try
{
final
doubleValue
=
double
.
parse
(
this
);
return
doubleValue
%
1
==
0
?
doubleValue
.
toInt
()
:
doubleValue
;
}
catch
(
e
)
{
print
(
'❌ String to Int parse failed for "
$this
":
$e
'
);
return
0
;
}
}
}
\ No newline at end of file
lib/networking/restful_api_request.dart
View file @
7777aa65
...
...
@@ -21,6 +21,10 @@ import '../screen/otp/model/create_otp_response_model.dart';
import
'../screen/otp/model/otp_verify_response_model.dart'
;
import
'../screen/pageDetail/model/campaign_detail_model.dart'
;
import
'../screen/pageDetail/model/detail_page_rule_type.dart'
;
import
'../screen/shopping/model/affiliate_brand_model.dart'
;
import
'../screen/shopping/model/affiliate_category_model.dart'
;
import
'../screen/shopping/model/affiliate_product_top_sale_model.dart'
;
import
'../screen/shopping/model/cashback_overview_model.dart'
;
import
'../screen/splash/splash_screen_viewmodel.dart'
;
import
'../screen/voucher/models/like_product_reponse_model.dart'
;
import
'../screen/voucher/models/product_store_model.dart'
;
...
...
@@ -310,4 +314,37 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return
GameBundleItemModel
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
List
<
AffiliateCategoryModel
>>>
affiliateCategoryGetList
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
requestNormal
(
APIPaths
.
affiliateCategoryGetList
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateCategoryModel
.
fromJson
(
e
)).
toList
();
});
}
Future
<
BaseResponseModel
<
List
<
AffiliateBrandModel
>>>
affiliateBrandGetList
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
requestNormal
(
APIPaths
.
affiliateBrandGetList
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateBrandModel
.
fromJson
(
e
)).
toList
();
});
}
Future
<
BaseResponseModel
<
List
<
AffiliateProductTopSaleModel
>>>
affiliateProductTopSale
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
requestNormal
(
APIPaths
.
affiliateProductTopSale
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateProductTopSaleModel
.
fromJson
(
e
)).
toList
();
});
}
Future
<
BaseResponseModel
<
CashbackOverviewModel
>>
getCashBackOverview
()
async
{
return
requestNormal
(
APIPaths
.
getCashbackOverview
,
Method
.
GET
,
{},
(
data
)
{
return
CashbackOverviewModel
.
fromJson
(
data
as
Json
);
});
}
}
lib/screen/main_tab_screen/main_tab_screen.dart
View file @
7777aa65
...
...
@@ -3,7 +3,7 @@ import '../../resouce/base_color.dart';
import
'../game/game_tab_screen.dart'
;
import
'../home/home_screen.dart'
;
import
'../personal/personal_screen.dart'
;
import
'../shopping/
shopping
_tab_screen.dart'
;
import
'../shopping/
affiliate
_tab_screen.dart'
;
import
'../voucher/voucher_tab_screen.dart'
;
class
MainTabScreen
extends
StatefulWidget
{
...
...
@@ -20,7 +20,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
HomeScreen
(),
VoucherTabScreen
(),
GameTabScreen
(),
Shopping
TabScreen
(),
Affiliate
TabScreen
(),
PersonalScreen
(),
];
...
...
lib/screen/shopping/affiliate_overview.dart
0 → 100644
View file @
7777aa65
import
'package:flutter/material.dart'
;
import
'package:flutter_widget_from_html/flutter_widget_from_html.dart'
;
class
AffiliateOverviewPopup
extends
StatelessWidget
{
final
List
<
String
>
descriptions
;
const
AffiliateOverviewPopup
({
super
.
key
,
required
this
.
descriptions
});
@override
Widget
build
(
BuildContext
context
)
{
return
Dialog
(
insetPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
8
),
backgroundColor:
Colors
.
white
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
24
)),
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
4
),
// ✅ padding tổng thể
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
// Header
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
const
Spacer
(),
const
Expanded
(
flex:
6
,
child:
Text
(
'Hoàn điểm là gì?'
,
textAlign:
TextAlign
.
center
,
// ✅ căn giữa
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
24
),
),
),
Expanded
(
child:
IconButton
(
icon:
const
Icon
(
Icons
.
close
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
)
],
),
const
SizedBox
(
height:
12
),
// Descriptions
...
descriptions
.
map
(
(
html
)
=>
Padding
(
padding:
const
EdgeInsets
.
only
(
bottom:
12
),
child:
Container
(
width:
double
.
infinity
,
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
HtmlWidget
(
html
),
),
),
),
],
),
),
);
}
}
\ No newline at end of file
lib/screen/shopping/affiliate_tab_screen.dart
0 → 100644
View file @
7777aa65
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/shopping/sub_widget/build_affiliate_brand.dart'
;
import
'package:mypoint_flutter_app/screen/shopping/sub_widget/build_affiliate_category.dart'
;
import
'package:mypoint_flutter_app/screen/shopping/sub_widget/build_affiliate_product_topsale.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'affiliate_overview.dart'
;
import
'affiliate_tab_viewmodel.dart'
;
class
AffiliateTabScreen
extends
BaseScreen
{
const
AffiliateTabScreen
({
super
.
key
});
@override
State
<
AffiliateTabScreen
>
createState
()
=>
_AffiliateTabScreenState
();
}
class
_AffiliateTabScreenState
extends
BaseState
<
AffiliateTabScreen
>
with
BasicState
{
final
AffiliateTabViewModel
viewModel
=
Get
.
put
(
AffiliateTabViewModel
());
@override
Widget
createBody
()
{
return
Scaffold
(
backgroundColor:
Colors
.
grey
.
shade50
,
appBar:
CustomNavigationBar
(
title:
"Mua sắm"
,
showBackButton:
false
,
rightButtons:
[
IconButton
(
icon:
const
Icon
(
Icons
.
info
,
color:
Colors
.
white
),
onPressed:
()
{
final
descriptions
=
viewModel
.
overview
.
value
?.
descriptions
??
[];
if
(
descriptions
.
isNotEmpty
)
{
BottomSheetHelper
.
showBottomSheetPopup
(
child:
AffiliateOverviewPopup
(
descriptions:
viewModel
.
overview
.
value
?.
descriptions
??
[]),
);
}
},
),
],
),
body:
Obx
(()
{
if
(
viewModel
.
isLoading
.
value
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
return
RefreshIndicator
(
onRefresh:
viewModel
.
refreshData
,
child:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
fromLTRB
(
16
,
12
,
16
,
100
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisAlignment:
MainAxisAlignment
.
start
,
children:
[
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
amber
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
const
Text
(
"Điểm hoàn:"
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
normal
)),
const
SizedBox
(
width:
8
),
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
20
,
height:
20
),
const
SizedBox
(
width:
4
),
const
Text
(
"0"
,
style:
TextStyle
(
fontSize:
20
,
color:
BaseColor
.
primary400
,
fontWeight:
FontWeight
.
bold
)),
const
Spacer
(),
Icon
(
Icons
.
arrow_forward_ios
,
color:
BaseColor
.
primary400
,
size:
16
),
],
),
),
const
SizedBox
(
height:
16
),
SizedBox
(
height:
130
,
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
children:
[
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step1.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step2.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step3.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step4.png"
),
],
),
),
AffiliateBrand
(
brands:
viewModel
.
affiliateBrands
,),
AffiliateCategory
(
categories:
viewModel
.
affiliateCategories
,),
AffiliateProductTopSale
(
products:
viewModel
.
affiliateProducts
,),
],
),
),
);
}),
);
}
Widget
_buildStepCard
(
String
imagePath
)
{
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
16
),
child:
Image
.
asset
(
imagePath
,
fit:
BoxFit
.
cover
,
width:
250
),
);
}
}
lib/screen/shopping/affiliate_tab_viewmodel.dart
0 → 100644
View file @
7777aa65
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
'model/affiliate_brand_model.dart'
;
import
'model/affiliate_category_model.dart'
;
import
'model/affiliate_category_type.dart'
;
import
'model/affiliate_product_top_sale_model.dart'
;
import
'model/cashback_overview_model.dart'
;
class
AffiliateTabViewModel
extends
RestfulApiViewModel
{
final
RxList
<
AffiliateBrandModel
>
affiliateBrands
=
<
AffiliateBrandModel
>[].
obs
;
final
RxList
<
AffiliateCategoryModel
>
affiliateCategories
=
<
AffiliateCategoryModel
>[].
obs
;
final
RxList
<
AffiliateProductTopSaleModel
>
affiliateProducts
=
<
AffiliateProductTopSaleModel
>[].
obs
;
final
RxBool
isLoading
=
false
.
obs
;
var
overview
=
Rxn
<
CashbackOverviewModel
>();
@override
void
onInit
()
{
super
.
onInit
();
refreshData
();
_getAffiliateOverview
();
}
Future
<
void
>
refreshData
()
async
{
isLoading
.
value
=
true
;
await
Future
.
wait
([
_getAffiliateBrandGetList
(),
_getAffiliateCategoryGetList
(),
_getAffiliateProductTopSale
(),
]);
isLoading
.
value
=
false
;
}
Future
<
void
>
_getAffiliateBrandGetList
()
async
{
try
{
final
result
=
await
client
.
affiliateBrandGetList
();
affiliateBrands
.
value
=
(
result
.
data
??
[]).
take
(
6
).
toList
();
}
catch
(
error
)
{
print
(
"Error fetching affiliate brands:
$error
"
);
}
}
Future
<
void
>
_getAffiliateCategoryGetList
()
async
{
try
{
final
result
=
await
client
.
affiliateCategoryGetList
();
final
category
=
AffiliateCategoryModel
(
code:
AffiliateCategoryType
.
other
,
name:
"Khác"
,
);
final
data
=
(
result
.
data
??
[]).
take
(
7
).
toList
();
data
.
add
(
category
);
affiliateCategories
.
value
=
data
;
}
catch
(
error
)
{
print
(
"Error fetching affiliate brands:
$error
"
);
}
}
Future
<
void
>
_getAffiliateProductTopSale
()
async
{
try
{
final
result
=
await
client
.
affiliateProductTopSale
();
affiliateProducts
.
value
=
result
.
data
??
[];
}
catch
(
error
)
{
print
(
"Error fetching affiliate brands:
$error
"
);
}
}
Future
<
void
>
_getAffiliateOverview
()
async
{
try
{
final
result
=
await
client
.
getCashBackOverview
();
overview
.
value
=
result
.
data
;
}
catch
(
error
)
{
print
(
"Error fetching affiliate brands:
$error
"
);
}
}
}
\ No newline at end of file
lib/screen/shopping/model/
product
_brand_model.dart
→
lib/screen/shopping/model/
affiliate
_brand_model.dart
View file @
7777aa65
import
'package:json_annotation/json_annotation.dart'
;
part
'
product
_brand_model.g.dart'
;
part
'
affiliate
_brand_model.g.dart'
;
@JsonSerializable
()
class
Product
BrandModel
{
class
Affiliate
BrandModel
{
@JsonKey
(
name:
'brand_id'
)
final
String
brandId
;
@JsonKey
(
name:
'brand_name'
)
...
...
@@ -15,7 +15,7 @@ class ProductBrandModel {
final
String
?
categoryName
;
final
String
?
direction
;
Product
BrandModel
({
Affiliate
BrandModel
({
required
this
.
brandId
,
this
.
brandName
,
this
.
commision
,
...
...
@@ -25,6 +25,6 @@ class ProductBrandModel {
this
.
direction
,
});
factory
Product
BrandModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$
Product
BrandModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$
Product
BrandModelToJson
(
this
);
factory
Affiliate
BrandModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$
Affiliate
BrandModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$
Affiliate
BrandModelToJson
(
this
);
}
lib/screen/shopping/model/
product
_brand_model.g.dart
→
lib/screen/shopping/model/
affiliate
_brand_model.g.dart
View file @
7777aa65
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'
product
_brand_model.dart'
;
part of
'
affiliate
_brand_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Product
BrandModel
_$
Product
BrandModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
Product
BrandModel
(
Affiliate
BrandModel
_$
Affiliate
BrandModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
Affiliate
BrandModel
(
brandId:
json
[
'brand_id'
]
as
String
,
brandName:
json
[
'brand_name'
]
as
String
?,
commision:
json
[
'commision'
]
as
String
?,
...
...
@@ -17,13 +17,14 @@ ProductBrandModel _$ProductBrandModelFromJson(Map<String, dynamic> json) =>
direction:
json
[
'direction'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$ProductBrandModelToJson
(
ProductBrandModel
instance
)
=>
<
String
,
dynamic
>{
'brand_id'
:
instance
.
brandId
,
'brand_name'
:
instance
.
brandName
,
'commision'
:
instance
.
commision
,
'logo'
:
instance
.
logo
,
'brand_url'
:
instance
.
brandUrl
,
'category_name'
:
instance
.
categoryName
,
'direction'
:
instance
.
direction
,
};
Map
<
String
,
dynamic
>
_$AffiliateBrandModelToJson
(
AffiliateBrandModel
instance
,
)
=>
<
String
,
dynamic
>{
'brand_id'
:
instance
.
brandId
,
'brand_name'
:
instance
.
brandName
,
'commision'
:
instance
.
commision
,
'logo'
:
instance
.
logo
,
'brand_url'
:
instance
.
brandUrl
,
'category_name'
:
instance
.
categoryName
,
'direction'
:
instance
.
direction
,
};
lib/screen/shopping/model/affiliate_category_model.dart
0 → 100644
View file @
7777aa65
import
'package:json_annotation/json_annotation.dart'
;
import
'affiliate_category_type.dart'
;
part
'affiliate_category_model.g.dart'
;
@JsonSerializable
()
class
AffiliateCategoryModel
{
@JsonKey
(
name:
"category_code"
,
fromJson:
_codeFromJson
,
toJson:
_codeToJson
)
final
AffiliateCategoryType
code
;
@JsonKey
(
name:
"category_name"
)
final
String
name
;
AffiliateCategoryModel
({
required
this
.
code
,
required
this
.
name
,
});
static
AffiliateCategoryType
_codeFromJson
(
String
code
)
=>
affiliateCategoryTypeValues
[
code
]
??
AffiliateCategoryType
.
other
;
static
String
_codeToJson
(
AffiliateCategoryType
type
)
=>
affiliateCategoryTypeValues
.
entries
.
firstWhere
((
e
)
=>
e
.
value
==
type
,
orElse:
()
=>
const
MapEntry
(
"other"
,
AffiliateCategoryType
.
other
))
.
key
;
String
get
icon
=>
"ic_cashback_
${_codeToJson(code)}
"
;
bool
get
isOther
=>
code
==
AffiliateCategoryType
.
other
;
static
AffiliateCategoryModel
get
other
=>
AffiliateCategoryModel
(
code:
AffiliateCategoryType
.
other
,
name:
"Khác"
);
factory
AffiliateCategoryModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$AffiliateCategoryModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$AffiliateCategoryModelToJson
(
this
);
}
lib/screen/shopping/model/affiliate_category_model.g.dart
0 → 100644
View file @
7777aa65
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'affiliate_category_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AffiliateCategoryModel
_$AffiliateCategoryModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
AffiliateCategoryModel
(
code:
AffiliateCategoryModel
.
_codeFromJson
(
json
[
'category_code'
]
as
String
),
name:
json
[
'category_name'
]
as
String
,
);
Map
<
String
,
dynamic
>
_$AffiliateCategoryModelToJson
(
AffiliateCategoryModel
instance
,
)
=>
<
String
,
dynamic
>{
'category_code'
:
AffiliateCategoryModel
.
_codeToJson
(
instance
.
code
),
'category_name'
:
instance
.
name
,
};
lib/screen/shopping/model/affiliate_category_type.dart
0 → 100644
View file @
7777aa65
enum
AffiliateCategoryType
{
laptop
,
giayNu
,
meVaBe
,
doAn
,
sucKhoe
,
thoiTrangNu
,
giaoDuc
,
thoiTrangNam
,
dienTu
,
giaDung
,
nhaCuaCuocSong
,
phuKienDienThoai
,
dongHo
,
giayNam
,
camera
,
sacDep
,
tuiViNu
,
theThao
,
duLich
,
dichVu
,
other
,
}
const
affiliateCategoryTypeValues
=
{
"may-tinh-and-laptop"
:
AffiliateCategoryType
.
laptop
,
"giay-dep-nu"
:
AffiliateCategoryType
.
giayNu
,
"me-and-be"
:
AffiliateCategoryType
.
meVaBe
,
"do-an"
:
AffiliateCategoryType
.
doAn
,
"suc-khoe"
:
AffiliateCategoryType
.
sucKhoe
,
"thoi-trang-nu"
:
AffiliateCategoryType
.
thoiTrangNu
,
"giao-duc"
:
AffiliateCategoryType
.
giaoDuc
,
"thoi-trang-nam"
:
AffiliateCategoryType
.
thoiTrangNam
,
"thiet-bi-dien-tu"
:
AffiliateCategoryType
.
dienTu
,
"thiet-bi-dien-gia-dung"
:
AffiliateCategoryType
.
giaDung
,
"nha-cua-and-doi-song"
:
AffiliateCategoryType
.
nhaCuaCuocSong
,
"dien-thoai-and-phu-kien"
:
AffiliateCategoryType
.
phuKienDienThoai
,
"dong-ho"
:
AffiliateCategoryType
.
dongHo
,
"giay-dep-nam"
:
AffiliateCategoryType
.
giayNam
,
"may-anh-and-may-quay-phim"
:
AffiliateCategoryType
.
camera
,
"sac-dep"
:
AffiliateCategoryType
.
sacDep
,
"tui-vi-nu"
:
AffiliateCategoryType
.
tuiViNu
,
"the-thao"
:
AffiliateCategoryType
.
theThao
,
"du-lich"
:
AffiliateCategoryType
.
duLich
,
"dich-vu"
:
AffiliateCategoryType
.
dichVu
,
"khac"
:
AffiliateCategoryType
.
other
,
};
lib/screen/shopping/model/
brand
_model.dart
→
lib/screen/shopping/model/
affiliate_product_top_sale
_model.dart
View file @
7777aa65
import
'package:json_annotation/json_annotation.dart'
;
part
'
brand
_model.g.dart'
;
part
'
affiliate_product_top_sale
_model.g.dart'
;
@JsonSerializable
()
class
Brand
Model
{
class
AffiliateProductTopSale
Model
{
@JsonKey
(
name:
'product_name'
)
final
String
?
productName
;
@JsonKey
(
name:
'product_price'
)
...
...
@@ -17,7 +17,7 @@ class BrandModel {
final
String
?
quantitySold
;
final
String
?
direction
;
Brand
Model
({
AffiliateProductTopSale
Model
({
this
.
productName
,
this
.
productPrice
,
this
.
productLink
,
...
...
@@ -28,6 +28,6 @@ class BrandModel {
this
.
direction
,
});
factory
Brand
Model
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$
Brand
ModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$
Brand
ModelToJson
(
this
);
factory
AffiliateProductTopSale
Model
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$
AffiliateProductTopSale
ModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$
AffiliateProductTopSale
ModelToJson
(
this
);
}
lib/screen/shopping/model/
brand
_model.g.dart
→
lib/screen/shopping/model/
affiliate_product_top_sale
_model.g.dart
View file @
7777aa65
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'
brand
_model.dart'
;
part of
'
affiliate_product_top_sale
_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BrandModel
_$BrandModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
BrandModel
(
AffiliateProductTopSaleModel
_$AffiliateProductTopSaleModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
AffiliateProductTopSaleModel
(
productName:
json
[
'product_name'
]
as
String
?,
productPrice:
json
[
'product_price'
]
as
String
?,
productLink:
json
[
'product_link'
]
as
String
?,
...
...
@@ -17,14 +19,15 @@ BrandModel _$BrandModelFromJson(Map<String, dynamic> json) => BrandModel(
direction:
json
[
'direction'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$BrandModelToJson
(
BrandModel
instance
)
=>
<
String
,
dynamic
>{
'product_name'
:
instance
.
productName
,
'product_price'
:
instance
.
productPrice
,
'product_link'
:
instance
.
productLink
,
'commision'
:
instance
.
commision
,
'logo'
:
instance
.
logo
,
'thumnail_link'
:
instance
.
thumnailLink
,
'quantity_sold'
:
instance
.
quantitySold
,
'direction'
:
instance
.
direction
,
};
Map
<
String
,
dynamic
>
_$AffiliateProductTopSaleModelToJson
(
AffiliateProductTopSaleModel
instance
,
)
=>
<
String
,
dynamic
>{
'product_name'
:
instance
.
productName
,
'product_price'
:
instance
.
productPrice
,
'product_link'
:
instance
.
productLink
,
'commision'
:
instance
.
commision
,
'logo'
:
instance
.
logo
,
'thumnail_link'
:
instance
.
thumnailLink
,
'quantity_sold'
:
instance
.
quantitySold
,
'direction'
:
instance
.
direction
,
};
lib/screen/shopping/model/cashback_overview_model.dart
0 → 100644
View file @
7777aa65
import
'package:json_annotation/json_annotation.dart'
;
part
'cashback_overview_model.g.dart'
;
@JsonSerializable
()
class
CashbackOverviewModel
{
final
List
<
String
>?
descriptions
;
final
String
?
background
;
final
String
?
title
;
final
String
?
subtitle
;
CashbackOverviewModel
({
this
.
descriptions
,
this
.
background
,
this
.
title
,
this
.
subtitle
,
});
factory
CashbackOverviewModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$CashbackOverviewModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$CashbackOverviewModelToJson
(
this
);
}
lib/screen/shopping/model/cashback_overview_model.g.dart
0 → 100644
View file @
7777aa65
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'cashback_overview_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CashbackOverviewModel
_$CashbackOverviewModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
CashbackOverviewModel
(
descriptions:
(
json
[
'descriptions'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
e
as
String
)
.
toList
(),
background:
json
[
'background'
]
as
String
?,
title:
json
[
'title'
]
as
String
?,
subtitle:
json
[
'subtitle'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$CashbackOverviewModelToJson
(
CashbackOverviewModel
instance
,
)
=>
<
String
,
dynamic
>{
'descriptions'
:
instance
.
descriptions
,
'background'
:
instance
.
background
,
'title'
:
instance
.
title
,
'subtitle'
:
instance
.
subtitle
,
};
lib/screen/shopping/shopping_tab_screen.dart
deleted
100644 → 0
View file @
56e8b038
import
'package:flutter/material.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
class
ShoppingTabScreen
extends
StatefulWidget
{
const
ShoppingTabScreen
({
super
.
key
});
@override
State
<
ShoppingTabScreen
>
createState
()
=>
_ShoppingTabScreenState
();
}
class
_ShoppingTabScreenState
extends
State
<
ShoppingTabScreen
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
grey
.
shade50
,
appBar:
CustomNavigationBar
(
title:
"Mua sắm"
,
showBackButton:
false
,
rightButtons:
[
IconButton
(
icon:
const
Icon
(
Icons
.
info
,
color:
Colors
.
white
),
onPressed:
()
{
},
),
],
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisAlignment:
MainAxisAlignment
.
start
,
children:
[
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
amber
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
const
Text
(
"Điểm hoàn:"
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
normal
)),
const
SizedBox
(
width:
8
),
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
20
,
height:
20
),
const
SizedBox
(
width:
4
),
const
Text
(
"0"
,
style:
TextStyle
(
fontSize:
20
,
color:
BaseColor
.
primary400
,
fontWeight:
FontWeight
.
bold
)),
const
Spacer
(),
Icon
(
Icons
.
arrow_forward_ios
,
color:
BaseColor
.
primary400
,
size:
16
),
],
),
),
const
SizedBox
(
height:
16
),
SizedBox
(
height:
130
,
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
children:
[
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step1.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step2.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step3.png"
),
const
SizedBox
(
width:
12
),
_buildStepCard
(
"assets/images/banner_tutorial_refund_point_step4.png"
),
],
),
),
const
SizedBox
(
height:
24
),
Row
(
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
)),
],
),
const
SizedBox
(
height:
12
),
GridView
.
count
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
crossAxisCount:
3
,
childAspectRatio:
3
/
3.2
,
crossAxisSpacing:
8
,
mainAxisSpacing:
8
,
children:
[
_buildBrandItem
(
"Lazada"
,
"30%"
,
"assets/images/ic_pipi_06.png"
),
_buildBrandItem
(
"Newshop"
,
"5,3%"
,
"assets/images/ic_pipi_06.png"
),
_buildBrandItem
(
"Tiki"
,
"0,78%"
,
"assets/images/ic_pipi_06.png"
),
_buildBrandItem
(
"MyTour MyTour MyTour"
,
"3,1%"
,
"assets/images/ic_pipi_06.png"
),
_buildBrandItem
(
"Pierre Cardin"
,
"10,2%"
,
"assets/images/ic_pipi_06.png"
),
_buildBrandItem
(
"Adidas"
,
"2,9%"
,
"assets/images/ic_pipi_06.png"
),
],
),
Text
(
"Lĩnh Vực Hoàn Điểm"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
20
)),
const
SizedBox
(
height:
12
),
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
5
,
offset:
const
Offset
(
0
,
2
),
)
],
),
child:
GridView
.
count
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
crossAxisCount:
3
,
childAspectRatio:
3
/
3.2
,
// crossAxisSpacing: 8,
children:
[
_fieldCashBackItem
(
"Lazada"
,
"30%"
,
"assets/images/ic_pipi_06.png"
),
_fieldCashBackItem
(
"Newshop"
,
"5,3%"
,
"assets/images/ic_pipi_06.png"
),
_fieldCashBackItem
(
"Tiki"
,
"0,78%"
,
"assets/images/ic_pipi_06.png"
),
_fieldCashBackItem
(
"MyTour MyTour MyTour"
,
"3,1%"
,
"assets/images/ic_pipi_06.png"
),
_fieldCashBackItem
(
"Pierre Cardin"
,
"10,2%"
,
"assets/images/ic_pipi_06.png"
),
_fieldCashBackItem
(
"Adidas"
,
"2,9%"
,
"assets/images/ic_pipi_06.png"
),
],
),
),
],
),
),
);
}
Widget
_buildStepCard
(
String
imagePath
)
{
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
16
),
child:
Image
.
asset
(
imagePath
,
fit:
BoxFit
.
cover
,
width:
250
),
);
}
Widget
_fieldCashBackItem
(
String
name
,
String
cashback
,
String
logoPath
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2
;
return
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SizedBox
(
width:
imageWidth
,
height:
imageWidth
,
// ✅ 1:1 ratio
child:
Image
.
asset
(
logoPath
,
fit:
BoxFit
.
contain
),
),
const
SizedBox
(
height:
4
),
Text
(
textAlign:
TextAlign
.
center
,
name
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
,
fontSize:
16
,
color:
BaseColor
.
second500
),
),
],
),
);
},
);
}
Widget
_buildBrandItem
(
String
name
,
String
cashback
,
String
logoPath
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2
;
return
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
5
,
offset:
const
Offset
(
0
,
2
),
)
],
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SizedBox
(
width:
imageWidth
,
height:
imageWidth
,
// ✅ 1:1 ratio
child:
Image
.
asset
(
logoPath
,
fit:
BoxFit
.
contain
),
),
const
SizedBox
(
height:
4
),
Text
(
name
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
,
fontSize:
16
),
),
const
SizedBox
(
height:
4
),
RichText
(
textAlign:
TextAlign
.
center
,
text:
TextSpan
(
style:
const
TextStyle
(
fontSize:
12
),
children:
[
const
TextSpan
(
text:
"Hoàn đến: "
,
style:
TextStyle
(
color:
Colors
.
grey
),
),
TextSpan
(
text:
cashback
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
,
),
),
],
),
),
],
),
);
},
);
}
}
lib/screen/shopping/shopping_tab_viewmodel.dart
deleted
100644 → 0
View file @
56e8b038
import
'../../base/restful_api_viewmodel.dart'
;
class
ShoppingTabViewModel
extends
RestfulApiViewModel
{
}
\ No newline at end of file
Prev
1
2
3
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