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
c8abf95b
Commit
c8abf95b
authored
Jun 20, 2025
by
DatHV
Browse files
update screen logic
parent
fda33894
Changes
89
Hide whitespace changes
Inline
Side-by-side
lib/model/auth/working_site_model.g.dart
View file @
c8abf95b
...
...
@@ -9,13 +9,51 @@ part of 'working_site_model.dart';
WorkingSiteModel
_$WorkingSiteModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
WorkingSiteModel
(
id:
json
[
'id'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
avatar:
json
[
'avatar'
]
as
String
?,
primaryMembership:
json
[
'primary_membership'
]
==
null
?
null
:
PrimaryMembershipModel
.
fromJson
(
json
[
'primary_membership'
]
as
Map
<
String
,
dynamic
>,
),
);
Map
<
String
,
dynamic
>
_$WorkingSiteModelToJson
(
WorkingSiteModel
instance
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'name'
:
instance
.
name
,
'avatar'
:
instance
.
avatar
,
'primary_membership'
:
instance
.
primaryMembership
,
};
PrimaryMembershipModel
_$PrimaryMembershipModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
PrimaryMembershipModel
(
membershipLevel:
json
[
'membership_level'
]
==
null
?
null
:
MembershipLevelShortModel
.
fromJson
(
json
[
'membership_level'
]
as
Map
<
String
,
dynamic
>,
),
);
Map
<
String
,
dynamic
>
_$PrimaryMembershipModelToJson
(
PrimaryMembershipModel
instance
,
)
=>
<
String
,
dynamic
>{
'membership_level'
:
instance
.
membershipLevel
};
MembershipLevelShortModel
_$MembershipLevelShortModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
MembershipLevelShortModel
(
levelId:
json
[
'levelId'
]
as
String
?,
levelCode:
json
[
'level_code'
]
as
String
?,
levelName:
json
[
'level_name'
]
as
String
?,
levelLogo:
json
[
'level_logo'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$MembershipLevelShortModelToJson
(
MembershipLevelShortModel
instance
,
)
=>
<
String
,
dynamic
>{
'levelId'
:
instance
.
levelId
,
'level_code'
:
instance
.
levelCode
,
'level_name'
:
instance
.
levelName
,
'level_logo'
:
instance
.
levelLogo
,
};
lib/networking/restful_api.dart
View file @
c8abf95b
...
...
@@ -15,6 +15,24 @@ class RestfulAPIClient {
RestfulAPIClient
(
this
.
_dio
);
Json
header
=
{};
Future
<
BaseResponseModel
<
T
>>
fetchObject
<
T
>(
String
path
,
T
Function
(
dynamic
json
)
fromJsonT
,
)
async
{
final
response
=
await
_dio
.
get
(
path
);
return
BaseResponseModel
<
T
>.
fromJson
(
response
.
data
,
fromJsonT
);
}
Future
<
BaseResponseModel
<
List
<
T
>>>
fetchList
<
T
>(
String
path
,
T
Function
(
dynamic
json
)
fromJson
,
)
async
{
final
res
=
await
_dio
.
get
(
path
);
return
BaseResponseModel
<
List
<
T
>>.
fromJson
(
res
.
data
,
(
json
)
{
return
(
json
as
List
).
map
((
e
)
=>
fromJson
(
e
)).
toList
();
});
}
Future
<
BaseResponseModel
<
T
>>
requestNormal
<
T
>(
String
path
,
Method
method
,
Json
params
,
CallbackReturn
<
T
,
dynamic
>
parser
)
async
{
final
result
=
await
request
<
BaseResponseModel
<
T
>>(
path
,
method
,
params
,
(
data
)
{
return
BaseResponseModel
<
T
>.
fromJson
(
data
,
(
json
)
=>
parser
(
json
));
...
...
lib/networking/restful_api_request.dart
View file @
c8abf95b
...
...
@@ -18,8 +18,12 @@ import '../screen/game/models/game_bundle_item_model.dart';
import
'../screen/home/models/achievement_model.dart'
;
import
'../screen/home/models/hover_data_model.dart'
;
import
'../screen/home/models/main_section_config_model.dart'
;
import
'../screen/home/models/my_product_model.dart'
;
import
'../screen/home/models/notification_unread_model.dart'
;
import
'../screen/home/models/pipi_detail_model.dart'
;
import
'../screen/location_address/models/district_address_model.dart'
;
import
'../screen/location_address/models/province_address_model.dart'
;
import
'../screen/membership/models/membership_info_response.dart'
;
import
'../screen/notification/models/category_notify_item_model.dart'
;
import
'../screen/notification/models/notification_detail_model.dart'
;
import
'../screen/notification/models/notification_list_data_model.dart'
;
...
...
@@ -139,7 +143,6 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}
Future
<
BaseResponseModel
<
ProfileResponseModel
>>
getUserProfile
()
async
{
var
deviceKey
=
await
DeviceInfo
.
getDeviceId
();
return
requestNormal
(
APIPaths
.
getUserInfo
,
Method
.
GET
,
{},
(
data
)
=>
ProfileResponseModel
.
fromJson
(
data
as
Json
));
}
...
...
@@ -505,14 +508,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
});
}
Future
<
BaseResponseModel
<
AchievementListResponse
>>
getAchievementList
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"achievement_type"
:
"Earn_Point"
,
"start"
:
0
,
"limit"
:
1000
,
};
Future
<
BaseResponseModel
<
AchievementListResponse
>>
getAchievementList
(
Json
body
)
async
{
return
requestNormal
(
APIPaths
.
achievementGetList
,
Method
.
POST
,
body
,
(
data
)
{
return
AchievementListResponse
.
fromJson
(
data
as
Json
);
});
...
...
@@ -525,14 +521,55 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}
Future
<
BaseResponseModel
<
NotificationUnreadData
>>
getNotificationUnread
()
async
{
return
requestNormal
(
APIPaths
.
getNotificationUnread
,
Method
.
POST
,
{},
(
data
)
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
requestNormal
(
APIPaths
.
getNotificationUnread
,
Method
.
POST
,
body
,
(
data
)
{
return
NotificationUnreadData
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
HeaderHomeModel
>>
getDynamicHeaderHome
()
async
{
return
requestNormal
(
APIPaths
.
getDynamicHeaderHome
,
Method
.
POS
T
,
{},
(
data
)
{
return
requestNormal
(
APIPaths
.
getDynamicHeaderHome
,
Method
.
GE
T
,
{},
(
data
)
{
return
HeaderHomeModel
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
List
<
MyProductModel
>>>
getCustomerProducts
(
Json
body
)
async
{
return
requestNormal
(
APIPaths
.
getCustomerProducts
,
Method
.
GET
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
MyProductModel
.
fromJson
(
e
)).
toList
();
});
}
Future
<
BaseResponseModel
<
EmptyCodable
>>
updateWorkerSiteProfile
(
Json
body
)
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
body
[
"access_token"
]
=
token
;
return
requestNormal
(
APIPaths
.
updateWorkerSiteProfile
,
Method
.
POST
,
body
,
(
data
)
{
return
EmptyCodable
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
ProvinceAddressResponse
>>
locationProvinceGetList
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"country_code2"
:
"VN"
};
return
requestNormal
(
APIPaths
.
locationProvinceGetList
,
Method
.
POST
,
body
,
(
data
)
{
return
ProvinceAddressResponse
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
DistrictAddressResponse
>>
locationDistrictGetList
(
String
provinceCode
)
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"province_code"
:
provinceCode
};
return
requestNormal
(
APIPaths
.
locationDistrictGetList
,
Method
.
POST
,
body
,
(
data
)
{
return
DistrictAddressResponse
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
MembershipInfoResponse
>>
getMembershipLevelInfo
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
requestNormal
(
APIPaths
.
getMembershipLevelInfo
,
Method
.
POST
,
body
,
(
data
)
{
return
MembershipInfoResponse
.
fromJson
(
data
as
Json
);
});
}
}
\ No newline at end of file
lib/preference/data_preference.dart
View file @
c8abf95b
...
...
@@ -23,7 +23,8 @@ class DataPreference {
_profile
=
ProfileResponseModel
.
fromJson
(
jsonDecode
(
profileJson
));
}
}
String
get
fullName
=>
_profile
?.
workerSite
?.
fullname
??
"Quý Khách"
;
String
?
get
rankName
=>
_profile
?.
workingSite
?.
primaryMembership
?.
membershipLevel
?.
levelName
??
""
;
String
?
get
token
=>
_loginToken
?.
accessToken
;
String
?
get
phone
=>
_profile
?.
workerSite
?.
phoneNumber
;
bool
get
logged
=>
(
token
??
""
).
isNotEmpty
;
...
...
lib/preference/package_info.dart
0 → 100644
View file @
c8abf95b
import
'package:package_info_plus/package_info_plus.dart'
;
class
AppInfoHelper
{
static
Future
<
String
>
get
version
async
{
final
info
=
await
PackageInfo
.
fromPlatform
();
return
info
.
version
;
// tương đương CFBundleShortVersionString
}
static
Future
<
String
>
get
buildNumber
async
{
final
info
=
await
PackageInfo
.
fromPlatform
();
return
info
.
buildNumber
;
// tương đương CFBundleVersion
}
}
lib/screen/achievement/achievement_list_screen.dart
View file @
c8abf95b
...
...
@@ -2,47 +2,115 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../home/models/achievement_model.dart'
;
import
'achievement_viewmodel.dart'
;
class
AchievementListScreen
extends
State
less
Widget
{
class
AchievementListScreen
extends
State
ful
Widget
{
const
AchievementListScreen
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
final
viewModel
=
Get
.
put
(
AchievementViewModel
());
State
<
AchievementListScreen
>
createState
()
=>
_AchievementListScreenState
();
}
class
_AchievementListScreenState
extends
State
<
AchievementListScreen
>
{
late
final
AchievementViewModel
_viewModel
;
String
_title
=
"Thử thách trúng quà"
;
bool
isPointHunting
=
false
;
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
bool
)
{
isPointHunting
=
args
;
}
if
(
isPointHunting
==
true
)
{
_title
=
"Săn điểm"
;
}
_viewModel
=
Get
.
put
(
AchievementViewModel
(
isPointHunting:
isPointHunting
));
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Thử thách trúng quà"
),
appBar:
CustomNavigationBar
(
title:
_title
),
body:
Obx
(()
{
final
items
=
viewModel
.
achievements
;
return
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
GridView
.
builder
(
itemCount:
items
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
childAspectRatio:
4
/
3
,
if
(
isPointHunting
)
{
return
_buildPointHuntingContent
();
}
else
{
return
_buildAchievementContent
();
}
}),
);
}
Widget
_buildAchievementContent
()
{
final
items
=
_viewModel
.
achievements
;
return
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
GridView
.
builder
(
itemCount:
items
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
childAspectRatio:
4
/
3
,
),
itemBuilder:
(
context
,
index
)
{
final
item
=
items
[
index
];
return
GestureDetector
(
onTap:
()
{
item
.
directionScreen
?.
begin
();
},
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
loadNetworkImage
(
url:
item
.
urlBackground
??
''
,
placeholderAsset:
'assets/images/bg_default_34.png'
,
),
),
);
},
),
);
}
Widget
_buildPointHuntingContent
()
{
final
items
=
_viewModel
.
achievements
;
return
RefreshIndicator
(
onRefresh:
()
=>
_viewModel
.
fetchAchievements
(),
child:
CustomScrollView
(
physics:
const
AlwaysScrollableScrollPhysics
(),
slivers:
[
SliverToBoxAdapter
(
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
32
,
vertical:
12
),
child:
Image
.
asset
(
"assets/images/bg_header_hunt_points.png"
),
),
itemBuilder:
(
context
,
index
)
{
),
SliverList
(
delegate:
SliverChildBuilderDelegate
((
context
,
index
)
{
final
item
=
items
[
index
];
return
GestureDetector
(
onTap:
()
{
item
.
directionScreen
?.
begin
();
},
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
loadNetworkImage
(
url:
item
.
urlBackground
??
''
,
placeholderAsset:
'assets/images/bg_default_34.png'
,
),
),
);
},
return
_buildPointHuntingItem
(
item
);
},
childCount:
items
.
length
),
),
);
}),
],
),
);
}
Widget
_buildPointHuntingItem
(
AchievementModel
item
)
{
return
GestureDetector
(
onTap:
()
{
item
.
directionScreen
?.
begin
();
},
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
16
,
right:
16
,
top:
8
,
bottom:
8
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
loadNetworkImage
(
url:
item
.
urlBackground
??
''
,
placeholderAsset:
'assets/images/bg_default_169.png'
),
),
),
);
}
}
lib/screen/achievement/achievement_viewmodel.dart
View file @
c8abf95b
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../home/models/achievement_model.dart'
;
class
AchievementViewModel
extends
RestfulApiViewModel
{
bool
isPointHunting
;
var
achievements
=
<
AchievementModel
>[].
obs
;
AchievementViewModel
({
this
.
isPointHunting
=
false
});
@override
void
onInit
()
{
...
...
@@ -13,9 +16,16 @@ class AchievementViewModel extends RestfulApiViewModel {
}
Future
<
void
>
fetchAchievements
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"achievement_type"
:
isPointHunting
?
"Hunt_Point"
:
"Earn_Point"
,
"start"
:
0
,
"limit"
:
1000
,
};
showLoading
();
try
{
final
response
=
await
client
.
getAchievementList
();
final
response
=
await
client
.
getAchievementList
(
body
);
if
(
response
.
data
!=
null
)
{
achievements
.
value
=
response
.
data
?.
achievements
??
[];
}
...
...
lib/screen/game/game_tab_screen.dart
View file @
c8abf95b
...
...
@@ -5,6 +5,7 @@ import '../../base/basic_state.dart';
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'game_tab_viewmodel.dart'
;
class
GameTabScreen
extends
BaseScreen
{
...
...
@@ -15,6 +16,7 @@ class GameTabScreen extends BaseScreen {
}
class
_GameTabScreenState
extends
BaseState
<
GameTabScreen
>
with
BasicState
{
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
GameTabViewModel
_viewModel
=
Get
.
put
(
GameTabViewModel
());
final
LayerLink
_layerLink
=
LayerLink
();
final
GlobalKey
_infoKey
=
GlobalKey
();
...
...
@@ -45,6 +47,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
appBar:
CustomNavigationBar
(
title:
"Games"
,
showBackButton:
false
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
rightButtons:
[
CompositedTransformTarget
(
link:
_layerLink
,
...
...
lib/screen/home/custom_widget/achievement_carousel_widget.dart
View file @
c8abf95b
import
'package:flutter/
cupertino
.dart'
;
import
'package:flutter/
material
.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/achievement_model.dart'
;
import
'../models/main_section_config_model.dart'
;
class
AchievementCarousel
extends
StatelessWidget
{
final
List
<
AchievementModel
>
items
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
AchievementModel
)?
onTap
;
const
AchievementCarousel
({
super
.
key
,
required
this
.
items
,
this
.
onTap
});
const
AchievementCarousel
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
if
(
items
.
isEmpty
)
return
const
SizedBox
.
shrink
();
return
SizedBox
(
height:
width
*
180
/
230
/
1.6
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
items
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
AchievementCard
(
achievement:
items
[
index
],
onTap:
()
=>
onTap
?.
call
(
items
[
index
]),
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
SizedBox
(
height:
width
*
180
/
230
/
1.6
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
items
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
AchievementCard
(
achievement:
items
[
index
],
onTap:
()
=>
onTap
?.
call
(
items
[
index
]),
),
),
),
)
,
]
,
);
}
}
...
...
lib/screen/home/custom_widget/affiliate_brand_grid_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'../../shopping/model/affiliate_brand_model.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
class
AffiliateBrandGridWidget
extends
StatelessWidget
{
final
List
<
AffiliateBrandModel
>
affiliateBrands
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
AffiliateBrandModel
)?
onTap
;
const
AffiliateBrandGridWidget
({
super
.
key
,
required
this
.
affiliateBrands
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
if
(
affiliateBrands
.
isEmpty
)
{
return
const
SizedBox
.
shrink
();
}
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
GridView
.
builder
(
padding:
EdgeInsets
.
all
(
12
),
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
affiliateBrands
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
3
,
childAspectRatio:
3
/
3
,
crossAxisSpacing:
12
,
mainAxisSpacing:
12
,
),
itemBuilder:
(
context
,
index
)
{
final
brand
=
affiliateBrands
[
index
];
return
_buildAffiliateBrandItem
(
brand
);
},
),
],
);
}
Widget
_buildAffiliateBrandItem
(
AffiliateBrandModel
brand
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
2.5
;
return
Container
(
padding:
const
EdgeInsets
.
all
(
4
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.08
),
blurRadius:
5
,
offset:
const
Offset
(-
2
,
2
),
)
],
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SizedBox
(
width:
imageWidth
,
height:
imageWidth
,
// ✅ 1:1 tỉ lệ
child:
ClipOval
(
child:
Image
.
network
(
brand
.
logo
??
''
,
fit:
BoxFit
.
contain
,
errorBuilder:
(
_
,
__
,
___
)
=>
const
Icon
(
Icons
.
broken_image
),
),
),
),
const
SizedBox
(
height:
2
),
Text
(
brand
.
brandName
??
""
,
maxLines:
2
,
textAlign:
TextAlign
.
center
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
,
fontSize:
14
),
),
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:
"
${(brand.commision ?? '').toNum()?.toString() ?? ''}
%"
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
,
),
),
],
),
),
],
),
);
},
);
}
}
lib/screen/home/custom_widget/banner_carousel_widget.dart
View file @
c8abf95b
import
'dart:async'
;
import
'package:flutter/material.dart'
;
import
'package:infinite_carousel/infinite_carousel.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/banner_model.dart'
;
import
'../models/main_section_config_model.dart'
;
class
BannerCarousel
extends
StatefulWidget
{
final
List
<
String
>
imageUrls
;
final
List
<
BannerModel
>
banners
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
BannerModel
)?
onTap
;
const
BannerCarousel
({
super
.
key
,
required
this
.
imageUrls
});
const
BannerCarousel
({
super
.
key
,
required
this
.
banners
,
this
.
sectionConfig
,
this
.
onTap
});
@override
State
<
BannerCarousel
>
createState
()
=>
_BannerCarouselState
();
...
...
@@ -49,66 +54,82 @@ class _BannerCarouselState extends State<BannerCarousel> {
_controller
.
dispose
();
super
.
dispose
();
}
//343/135
_handleTapRightButton
()
{
widget
.
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
*
0.9
;
return
GestureDetector
(
onPanDown:
(
_
)
=>
_pauseAutoPlayTemporarily
(),
child:
SizedBox
(
height:
width
*
135
/
343
+
16
,
child:
Stack
(
alignment:
Alignment
.
bottomCenter
,
children:
[
InfiniteCarousel
.
builder
(
itemCount:
widget
.
imageUrls
.
length
,
itemExtent:
width
,
scrollBehavior:
ScrollConfiguration
.
of
(
context
).
copyWith
(
scrollbars:
false
),
loop:
true
,
center:
true
,
anchor:
0.0
,
velocityFactor:
0.1
,
// ✅ fix lỗi: snap từng page
controller:
_controller
,
onIndexChanged:
(
index
)
=>
setState
(()
=>
_currentIndex
=
index
%
widget
.
imageUrls
.
length
),
itemBuilder:
(
context
,
itemIndex
,
realIndex
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
4.0
,
vertical:
8.0
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
widget
.
imageUrls
[
itemIndex
%
widget
.
imageUrls
.
length
],
fit:
BoxFit
.
cover
,
width:
double
.
infinity
,
),
),
);
},
),
Positioned
(
bottom:
12
,
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
widget
.
imageUrls
.
asMap
().
entries
.
map
((
entry
)
{
return
GestureDetector
(
onTap:
()
=>
_controller
.
animateToItem
(
entry
.
key
,
duration:
const
Duration
(
milliseconds:
500
)),
child:
Container
(
width:
8.0
,
height:
8.0
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
4.0
),
decoration:
BoxDecoration
(
shape:
BoxShape
.
circle
,
color:
_currentIndex
==
entry
.
key
?
Colors
.
white
:
Colors
.
white
.
withOpacity
(
0.4
),
return
Column
(
children:
[
if
((
widget
.
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
widget
.
sectionConfig
?.
name
??
""
,
onViewAll:
widget
.
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
GestureDetector
(
onPanDown:
(
_
)
=>
_pauseAutoPlayTemporarily
(),
child:
SizedBox
(
height:
width
*
135
/
343
+
16
,
child:
Stack
(
alignment:
Alignment
.
bottomCenter
,
children:
[
InfiniteCarousel
.
builder
(
itemCount:
widget
.
banners
.
length
,
itemExtent:
width
,
scrollBehavior:
ScrollConfiguration
.
of
(
context
).
copyWith
(
scrollbars:
false
),
loop:
true
,
center:
true
,
anchor:
0.0
,
velocityFactor:
0.1
,
// ✅ fix lỗi: snap từng page
controller:
_controller
,
onIndexChanged:
(
index
)
=>
setState
(()
=>
_currentIndex
=
index
%
widget
.
banners
.
length
),
itemBuilder:
(
context
,
itemIndex
,
realIndex
)
{
return
GestureDetector
(
onTap:
()
=>
widget
.
onTap
?.
call
(
widget
.
banners
[
itemIndex
%
widget
.
banners
.
length
]),
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
4.0
,
vertical:
8.0
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
widget
.
banners
[
itemIndex
%
widget
.
banners
.
length
].
itemImage
??
""
,
fit:
BoxFit
.
cover
,
width:
double
.
infinity
,
),
),
),
),
);
}).
toList
(),
),
);
},
),
Positioned
(
bottom:
12
,
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
widget
.
banners
.
asMap
().
entries
.
map
((
entry
)
{
return
GestureDetector
(
onTap:
()
=>
_controller
.
animateToItem
(
entry
.
key
,
duration:
const
Duration
(
milliseconds:
500
)),
child:
Container
(
width:
8.0
,
height:
8.0
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
4.0
),
decoration:
BoxDecoration
(
shape:
BoxShape
.
circle
,
color:
_currentIndex
==
entry
.
key
?
Colors
.
white
:
Colors
.
white
.
withOpacity
(
0.4
),
),
),
);
}).
toList
(),
),
),
],
),
]
,
)
,
),
)
,
]
,
);
}
}
\ No newline at end of file
}
lib/screen/home/custom_widget/brand_grid_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../shopping/model/affiliate_brand_model.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/brand_model.dart'
;
import
'../models/main_section_config_model.dart'
;
class
BrandGridWidget
extends
StatelessWidget
{
final
List
<
BrandModel
>
brands
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
BrandModel
)?
onTap
;
const
BrandGridWidget
({
super
.
key
,
required
this
.
brands
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
if
(
brands
.
isEmpty
)
{
return
const
SizedBox
.
shrink
();
}
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
GridView
.
builder
(
padding:
EdgeInsets
.
all
(
0
),
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
brands
.
length
,
gridDelegate:
const
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
3
,
childAspectRatio:
3
/
3
,
crossAxisSpacing:
0
,
mainAxisSpacing:
0
,
),
itemBuilder:
(
context
,
index
)
{
final
brand
=
brands
[
index
];
return
_buildBrandItem
(
brand
,
index
);
},
),
],
);
}
Widget
_buildBrandItem
(
BrandModel
brand
,
int
index
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
final
double
imageWidth
=
constraints
.
maxWidth
/
3
;
return
Container
(
padding:
const
EdgeInsets
.
all
(
4
),
color:
index
%
2
!=
0
?
Colors
.
red
.
shade50
:
Colors
.
white
,
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
SizedBox
(
width:
imageWidth
,
height:
imageWidth
,
child:
loadNetworkImage
(
url:
brand
.
logo
??
""
,
fit:
BoxFit
.
contain
,
placeholderAsset:
"assets/images/bg_default_11.png"
,
),
),
),
const
SizedBox
(
height:
4
),
Text
(
textAlign:
TextAlign
.
center
,
brand
.
brandName
??
""
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w600
,
fontSize:
14
),
),
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:
brand
.
pointAccumulationRate
??
''
,
style:
const
TextStyle
(
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
),
),
],
),
),
],
),
);
},
);
}
}
lib/screen/home/custom_widget/flash_sale_carousel_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'../../voucher/models/product_model.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
import
'flash_sale_header_widget.dart'
;
class
FlashSaleCarouselWidget
extends
StatefulWidget
{
final
List
<
ProductModel
>
products
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
ProductModel
)?
onTap
;
const
FlashSaleCarouselWidget
({
super
.
key
,
required
this
.
products
,
this
.
sectionConfig
,
this
.
onTap
});
@override
State
<
FlashSaleCarouselWidget
>
createState
()
=>
_FlashSaleCarouselWidgetState
();
}
class
_FlashSaleCarouselWidgetState
extends
State
<
FlashSaleCarouselWidget
>
{
_handleTapRightButton
()
{
widget
.
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
widthItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
2.5
;
final
heightItem
=
widthItem
*
9
/
16
+
(
widget
.
products
.
firstOrNull
?.
extendSpaceFlashSaleItem
??
130
);
if
(
widget
.
products
.
isEmpty
)
return
const
SizedBox
.
shrink
();
return
Column
(
children:
[
if
(
widget
.
sectionConfig
?.
flashSale
!=
null
)
FlashSaleHeader
(
flashSale:
widget
.
sectionConfig
?.
flashSale
,
onViewAll:
widget
.
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
const
SizedBox
(
height:
8
),
ConstrainedBox
(
constraints:
BoxConstraints
(
maxHeight:
heightItem
),
child:
ListView
.
separated
(
shrinkWrap:
true
,
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
widget
.
products
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
_buildFlashSaleGridItem
(
widget
.
products
[
index
]),
),
),
],
);
}
Widget
_buildFlashSaleGridItem
(
ProductModel
product
)
{
final
widthItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
2.5
;
return
GestureDetector
(
onTap:
()
=>
widget
.
onTap
?.
call
(
product
),
child:
Container
(
width:
widthItem
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
0
),
padding:
const
EdgeInsets
.
all
(
0
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
grey
.
shade200
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
// Hình ảnh + tag % giảm
Stack
(
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
only
(
topLeft:
Radius
.
circular
(
8
),
topRight:
Radius
.
circular
(
8
)),
child:
Image
.
network
(
product
.
banner
?.
url
??
''
,
width:
double
.
infinity
,
height:
widthItem
*
9
/
16
,
fit:
BoxFit
.
cover
,
),
),
if
(
product
.
percentDiscount
!=
null
)
Positioned
(
right:
4
,
bottom:
4
,
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
red
,
borderRadius:
BorderRadius
.
circular
(
20
)),
child:
Text
(
'-
${product.percentDiscount}
%'
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
,
fontSize:
12
),
),
),
),
],
),
// const SizedBox(height: 8),
// Giá khuyến mãi + gạch giá gốc
Container
(
padding:
const
EdgeInsets
.
all
(
4
),
child:
Column
(
children:
[
SizedBox
(
height:
24
,
child:
Row
(
children:
[
Image
.
asset
(
"assets/images/ic_hot_flash_sale.png"
,
width:
20
,
height:
20
,
fit:
BoxFit
.
cover
),
// if (product.previewFlashSale?.price != null)
Text
(
"
${product.amountToBePaid?.money(CurrencyUnit.noneSpace)}
đ"
,
// '${product.previewFlashSale?.price}đ',
style:
const
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
),
),
const
SizedBox
(
width:
2
),
if
(
product
.
price
?.
value
!=
null
)
Text
(
'
${product.price?.value?.money(CurrencyUnit.noneSpace)}
đ'
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
grey
,
decoration:
TextDecoration
.
lineThrough
,
),
),
],
),
),
const
SizedBox
(
height:
4
),
Text
(
product
.
name
??
''
,
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontSize:
13
),
),
const
SizedBox
(
height:
4
),
// Button Nhận quà / điểm
if
(
product
.
previewFlashSale
?.
rewardContent
!=
null
)
Align
(
alignment:
Alignment
.
centerLeft
,
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
,
vertical:
4
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
orange
),
borderRadius:
BorderRadius
.
circular
(
20
),
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Image
.
asset
(
product
.
previewFlashSale
?.
imageReward
??
'assets/images/ic_gift_flash_sale.png'
,
width:
16
,
height:
16
,
color:
Colors
.
orange
,
),
const
SizedBox
(
width:
4
),
Text
(
product
.
previewFlashSale
?.
rewardContent
??
'Nhận quà'
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
orange
,
fontWeight:
FontWeight
.
bold
),
),
],
),
),
),
// Thanh tiến trình + Số lượng đã bán
if
(
product
.
isShowProsessSoldItem
)
Column
(
children:
[
const
SizedBox
(
height:
4
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
// Thanh tiến trình
SizedBox
(
width:
widthItem
-
90
,
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
4
),
child:
LinearProgressIndicator
(
value:
product
.
progress
,
minHeight:
6
,
backgroundColor:
Colors
.
grey
.
shade300
,
valueColor:
AlwaysStoppedAnimation
<
Color
>(
product
.
inStock
?
Colors
.
orange
:
Colors
.
red
,
),
),
),
),
const
SizedBox
(
width:
2
),
Text
(
product
.
inStock
?
'Đã bán
${product.previewFlashSale?.fsQuantitySold ?? 0}
'
:
'Đã bán hết'
,
style:
TextStyle
(
fontSize:
12
,
color:
product
.
inStock
?
Colors
.
black
:
Colors
.
grey
),
),
],
),
],
),
],
),
),
],
),
),
);
}
}
lib/screen/home/custom_widget/flash_sale_header_widget.dart
0 → 100644
View file @
c8abf95b
import
'dart:async'
;
import
'package:flutter/material.dart'
;
import
'../../flash_sale/preview_flash_sale_model.dart'
;
class
FlashSaleHeader
extends
StatefulWidget
{
final
PreviewFlashSale
?
flashSale
;
final
VoidCallback
?
onViewAll
;
const
FlashSaleHeader
({
super
.
key
,
required
this
.
flashSale
,
this
.
onViewAll
,
});
@override
State
<
FlashSaleHeader
>
createState
()
=>
_FlashSaleHeaderState
();
}
class
_FlashSaleHeaderState
extends
State
<
FlashSaleHeader
>
{
late
Duration
_remaining
;
Timer
?
_timer
;
@override
void
initState
()
{
super
.
initState
();
_remaining
=
widget
.
flashSale
?.
countdownLocal
??
Duration
(
seconds:
100000
);
_startTimer
();
}
void
_startTimer
()
{
_timer
?.
cancel
();
_timer
=
Timer
.
periodic
(
const
Duration
(
seconds:
1
),
(
_
)
{
if
(
_remaining
.
inSeconds
<=
0
)
{
_timer
?.
cancel
();
}
else
{
setState
(()
{
_remaining
-=
const
Duration
(
seconds:
1
);
});
}
});
}
String
_formatTime
(
int
value
)
=>
value
.
toString
().
padLeft
(
2
,
'0'
);
Widget
_buildTimeBox
(
String
text
)
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
3
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
red
.
shade400
,
borderRadius:
BorderRadius
.
circular
(
4
),
),
child:
Text
(
text
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontWeight:
FontWeight
.
bold
),
),
);
}
@override
void
dispose
()
{
_timer
?.
cancel
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
hours
=
_formatTime
(
_remaining
.
inHours
);
final
minutes
=
_formatTime
(
_remaining
.
inMinutes
.
remainder
(
60
));
final
seconds
=
_formatTime
(
_remaining
.
inSeconds
.
remainder
(
60
));
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
child:
Row
(
children:
[
Image
.
asset
(
"assets/images/ic_flash_sale.png"
,
height:
24
,
fit:
BoxFit
.
cover
,),
const
SizedBox
(
width:
6
),
Text
(
widget
.
flashSale
?.
desTime
??
""
,
style:
TextStyle
(
fontSize:
14
)),
const
SizedBox
(
width:
4
),
_buildTimeBox
(
hours
),
const
SizedBox
(
width:
2
),
const
Text
(
":"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
const
SizedBox
(
width:
2
),
_buildTimeBox
(
minutes
),
const
SizedBox
(
width:
2
),
const
Text
(
":"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
const
SizedBox
(
width:
2
),
_buildTimeBox
(
seconds
),
const
Spacer
(),
if
(
widget
.
onViewAll
!=
null
)
GestureDetector
(
onTap:
widget
.
onViewAll
,
child:
Text
(
'Xem tất cả'
,
style:
TextStyle
(
color:
Colors
.
blue
[
700
],
fontWeight:
FontWeight
.
bold
),
),
),
],
),
);
}
}
lib/screen/home/custom_widget/header_home.dart
View file @
c8abf95b
...
...
@@ -4,41 +4,38 @@ import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import
'../../../preference/data_preference.dart'
;
import
'../../../preference/point/header_home_model.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../models/notification_unread_model.dart'
;
class
HomeGreetingHeader
extends
StatelessWidget
{
final
double
?
heightContent
;
HeaderHomeModel
dataHeader
;
String
level
=
"Hạng Đồng"
;
NotificationUnreadData
?
notificationUnreadData
;
HomeGreetingHeader
({
super
.
key
,
this
.
heightContent
,
required
this
.
dataHeader
,
});
HomeGreetingHeader
({
super
.
key
,
this
.
heightContent
,
required
this
.
dataHeader
,
required
this
.
notificationUnreadData
});
@override
Widget
build
(
BuildContext
context
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
heightSize
=
heightContent
??
(
width
*
86
/
375
+
112
);
final
name
=
DataPreference
.
instance
.
profile
?.
workingSite
?.
name
??
"Quý Khách"
;
double
heightWhiteBox
=
112
;
final
name
=
DataPreference
.
instance
.
fullName
;
final
level
=
DataPreference
.
instance
.
rankName
??
"Hạng Đồng"
;
double
heightWhiteBox
=
112
;
return
Stack
(
children:
[
Container
(
SizedBox
(
height:
heightSize
,
width:
double
.
infinity
,
decoration:
const
BoxDecoration
(
image:
DecorationImage
(
image:
AssetImage
(
'assets/images/bg_header_navi.png'
),
fit:
BoxFit
.
cover
),
child:
loadNetworkImage
(
url:
dataHeader
.
background
,
fit:
BoxFit
.
cover
,
placeholderAsset:
"assets/images/bg_header_navi.png"
,
),
// child: loadNetworkImage(url: dataHeader.background, fit: BoxFit.cover, placeholderAsset: "assets/images/bg_header_navi.png"),
),
Positioned
(
bottom:
heightWhiteBox
+
16
,
left:
16
,
child:
Image
.
asset
(
'assets/images/ic_logo_mypoint.png'
,
height:
24
,
),
child:
Image
.
asset
(
'assets/images/ic_logo_mypoint.png'
,
height:
24
),
),
Positioned
(
left:
0
,
...
...
@@ -79,7 +76,7 @@ class HomeGreetingHeader extends StatelessWidget {
child:
Stack
(
children:
[
Image
.
asset
(
'assets/images/ic_notify_black.png'
,
width:
32
,
height:
32
),
if
(
1
>
0
)
if
(
(
notificationUnreadData
?.
unread
??
0
)
>
0
)
Positioned
(
right:
6
,
top:
4
,
...
...
@@ -100,7 +97,11 @@ class HomeGreetingHeader extends StatelessWidget {
const
SizedBox
(
height:
2
),
Row
(
children:
[
_buildStatItem
(
icon:
"assets/images/ic_point_gray.png"
,
value:
dataHeader
.
totalPointActive
.
toString
(),
onTap:
_onPointTap
),
_buildStatItem
(
icon:
"assets/images/ic_point_gray.png"
,
value:
dataHeader
.
totalPointActive
.
toString
(),
onTap:
_onPointTap
,
),
SizedBox
(
width:
12
),
_buildStatItem
(
icon:
"assets/images/ic_voucher_gray.png"
,
...
...
@@ -124,10 +125,7 @@ class HomeGreetingHeader extends StatelessWidget {
onTap:
onTap
,
child:
Container
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
4
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
20
),
border:
Border
.
all
(
color:
Colors
.
black26
),
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
20
),
border:
Border
.
all
(
color:
Colors
.
black26
)),
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
...
...
@@ -153,10 +151,12 @@ class HomeGreetingHeader extends StatelessWidget {
}
_onMyVoucherTap
()
{
Get
.
toNamed
(
myVoucherListScreen
);
print
(
"_onMyVoucherTap"
);
}
_onRankTap
()
{
Get
.
toNamed
(
membershipScreen
);
print
(
"_onRankTap"
);
}
}
lib/screen/home/custom_widget/home_header_widget.dart
View file @
c8abf95b
...
...
@@ -99,17 +99,7 @@ class _HomeScreenWithHeaderState extends State<HomeScreenWithHeader> {
color:
Colors
.
white
,
child:
Column
(
children:
[
BannerCarousel
(
imageUrls:
[
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/F31FF2E775D7BFC940156709FB79E883/1746430303'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/1B67CFBF96BD24929EB10F1853A47651/1740708747'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/29B40B1E04EBEC8A1C1F9D13C0194A27/1735194572'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/C29872C4F95B280B880DE45BC07E7DE4/1693906872'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/F31FF2E775D7BFC940156709FB79E883/1746430303'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/F31FF2E775D7BFC940156709FB79E883/1746430303'
,
'https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/F31FF2E775D7BFC940156709FB79E883/1746430303'
,
],
),
BannerCarousel
(
banners:
[],),
if
(
_services
.
isNotEmpty
)
MainServiceGrid
(
services:
_services
,
...
...
lib/screen/home/custom_widget/main_service_grid_widget.dart
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
import
'../models/main_service_model.dart'
;
class
MainServiceGrid
extends
StatelessWidget
{
final
List
<
MainServiceModel
>
services
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
MainServiceModel
)?
onTap
;
const
MainServiceGrid
({
super
.
key
,
required
this
.
services
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
const
MainServiceGrid
({
super
.
key
,
required
this
.
services
,
this
.
onTap
,
this
.
sectionConfig
});
@override
Widget
build
(
BuildContext
context
)
{
return
SizedBox
(
height:
120
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
services
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
16
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
services
[
index
]),
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
SizedBox
(
height:
120
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
services
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
16
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
services
[
index
]),
),
),
),
)
,
]
,
);
}
...
...
lib/screen/home/custom_widget/my_product_carousel_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
import
'../models/my_product_model.dart'
;
class
MyProductCarouselWidget
extends
StatelessWidget
{
final
List
<
MyProductModel
>
items
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
MyProductModel
)?
onTap
;
const
MyProductCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
widthItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
1.2
;
if
(
items
.
isEmpty
)
return
const
SizedBox
.
shrink
();
return
Container
(
color:
Colors
.
white
,
child:
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
SizedBox
(
height:
widthItem
*
99
/
280
+
24
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
items
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
items
[
index
],
widthItem
),
),
),
],
),
);
}
Widget
_buildItem
(
BuildContext
context
,
MyProductModel
product
,
double
widthItem
)
{
return
GestureDetector
(
onTap:
()
=>
onTap
?.
call
(
product
),
child:
Container
(
width:
widthItem
,
margin:
const
EdgeInsets
.
symmetric
(
vertical:
16
,
horizontal:
0
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
border:
Border
.
all
(
color:
Colors
.
grey
.
shade200
),
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
black
.
withOpacity
(
0.05
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
2
))],
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
loadNetworkImage
(
url:
product
.
logo
??
''
,
width:
64
,
height:
64
,
fit:
BoxFit
.
cover
,
placeholderAsset:
"assets/images/bg_default_11.png"
,
),
),
const
SizedBox
(
width:
16
),
Expanded
(
child:
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
product
.
brandName
??
''
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
16
),
maxLines:
1
,
),
const
SizedBox
(
height:
2
),
Text
(
product
.
title
??
''
,
style:
const
TextStyle
(
fontSize:
13
,
color:
Colors
.
black87
),
maxLines:
2
,
overflow:
TextOverflow
.
ellipsis
,
),
const
SizedBox
(
height:
2
),
Text
(
'HSD:
${product.expire ?? ''}
'
,
style:
const
TextStyle
(
fontSize:
13
,
color:
Colors
.
black54
)),
],
),
),
),
],
),
),
);
}
}
lib/screen/home/custom_widget/news_carousel_widget.dart
View file @
c8abf95b
...
...
@@ -2,25 +2,41 @@ import 'package:flutter/material.dart';
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../faqs/faqs_model.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
class
NewsCarouselWidget
extends
StatelessWidget
{
final
List
<
PageItemModel
>
items
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
PageItemModel
)?
onTap
;
const
NewsCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
onTap
});
const
NewsCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
widthItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
1.6
;
if
(
items
.
isEmpty
)
return
const
SizedBox
.
shrink
();
return
SizedBox
(
height:
widthItem
*
9
/
16
+
72
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
items
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
items
[
index
]),
),
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
SizedBox
(
height:
widthItem
*
9
/
16
+
72
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
itemCount:
items
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
items
[
index
]),
),
),
],
);
}
...
...
lib/screen/home/custom_widget/product_grid_widget.dart
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../../widgets/custom_point_text_tag.dart'
;
import
'../../voucher/models/product_model.dart'
;
import
'../../voucher/sub_widget/voucher_section_title.dart'
;
import
'../models/main_section_config_model.dart'
;
class
ProductGrid
extends
StatelessWidget
{
final
List
<
ProductModel
>
products
;
final
MainSectionConfigModel
?
sectionConfig
;
final
void
Function
(
ProductModel
)?
onTap
;
final
double
_spacing
=
12
;
const
ProductGrid
({
super
.
key
,
required
this
.
products
,
this
.
onTap
});
const
ProductGrid
({
super
.
key
,
required
this
.
products
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
widthItem
=
(
width
-
_spacing
*
3
)/
2
;
return
GridView
.
builder
(
physics:
const
NeverScrollableScrollPhysics
(),
shrinkWrap:
true
,
padding:
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
16
),
itemCount:
products
.
length
,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
childAspectRatio:
widthItem
/
(
widthItem
*
9
/
16
+
94
),
mainAxisSpacing:
_spacing
,
crossAxisSpacing:
_spacing
,
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
products
[
index
]),
return
Column
(
children:
[
if
((
sectionConfig
?.
name
??
""
).
isNotEmpty
)
HeaderSectionTitle
(
title:
sectionConfig
?.
name
??
""
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
GridView
.
builder
(
physics:
const
NeverScrollableScrollPhysics
(),
shrinkWrap:
true
,
padding:
EdgeInsets
.
only
(
left:
16
,
right:
16
,
top:
8
,
bottom:
24
),
//EdgeInsets.symmetric(horizontal: 16, vertical: 16),
itemCount:
products
.
length
,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
childAspectRatio:
widthItem
/
(
widthItem
*
9
/
16
+
94
),
mainAxisSpacing:
_spacing
,
crossAxisSpacing:
_spacing
,
),
itemBuilder:
(
context
,
index
)
=>
_buildItem
(
context
,
products
[
index
]),
),
],
);
}
...
...
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