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
Show whitespace changes
Inline
Side-by-side
lib/screen/voucher/my_voucher/my_product_list_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../widgets/custom_app_bar.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../../home/models/my_product_model.dart'
;
import
'my_product_list_viewmodel.dart'
;
import
'package:dotted_border/dotted_border.dart'
;
class
MyVoucherListScreen
extends
StatefulWidget
{
const
MyVoucherListScreen
({
super
.
key
});
@override
State
<
MyVoucherListScreen
>
createState
()
=>
_MyVoucherListScreenState
();
}
class
_MyVoucherListScreenState
extends
State
<
MyVoucherListScreen
>
{
late
final
MyProductListViewModel
_viewModel
;
@override
void
initState
()
{
super
.
initState
();
_viewModel
=
Get
.
put
(
MyProductListViewModel
());
}
@override
Widget
build
(
BuildContext
context
)
{
final
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
return
Scaffold
(
appBar:
CustomAppBar
.
back
(
title:
'Ưu đãi của tôi'
),
body:
Obx
(
()
=>
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceAround
,
children:
[
_buildTab
(
'Đang có'
,
0
),
_buildTab
(
'Đã sử dụng'
,
1
),
_buildTab
(
'Hết hạn'
,
2
)],
),
const
Divider
(
height:
1
),
if
(
_viewModel
.
myProducts
.
isEmpty
)
Expanded
(
child:
EmptyWidget
(
size:
Size
(
screenWidth
/
2
,
screenWidth
/
2
)))
else
Expanded
(
child:
RefreshIndicator
(
onRefresh:
()
async
{
_viewModel
.
freshData
(
isRefresh:
true
);
},
child:
ListView
.
builder
(
padding:
const
EdgeInsets
.
all
(
12
),
itemCount:
_viewModel
.
myProducts
.
length
,
itemBuilder:
(
_
,
index
)
{
if
(
index
>=
_viewModel
.
myProducts
.
length
)
{
_viewModel
.
freshData
(
isRefresh:
false
);
return
const
Center
(
child:
Padding
(
padding:
EdgeInsets
.
all
(
16
),
child:
CircularProgressIndicator
()),
);
}
final
product
=
_viewModel
.
myProducts
[
index
];
return
_buildVoucherItem
(
product
);
},
),
),
),
],
),
),
);
}
Widget
_buildTab
(
String
title
,
int
index
)
{
return
GestureDetector
(
onTap:
()
=>
_viewModel
.
selectTab
(
index
),
child:
Obx
(
()
=>
Column
(
children:
[
Text
(
title
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w600
,
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
(
MyProductModel
product
)
{
return
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
voucherDetailScreen
,
arguments:
{
"customerProductId"
:
product
.
id
});
},
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
padding:
const
EdgeInsets
.
all
(
0
),
child:
DottedBorder
(
color:
Colors
.
redAccent
.
withOpacity
(
0.6
),
borderType:
BorderType
.
RRect
,
radius:
const
Radius
.
circular
(
12
),
dashPattern:
const
[
6
,
4
],
strokeWidth:
1
,
child:
Container
(
padding:
const
EdgeInsets
.
all
(
12
),
child:
Row
(
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:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
product
.
brandName
??
''
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black54
,
fontSize:
12
),
),
const
SizedBox
(
height:
4
),
Text
(
product
.
title
??
''
,
style:
const
TextStyle
(
fontSize:
15
,
fontWeight:
FontWeight
.
w500
)),
const
SizedBox
(
height:
4
),
Text
(
'HSD:
${product.expire}
'
,
style:
const
TextStyle
(
color:
Colors
.
black54
,
fontSize:
12
)),
],
),
),
],
),
),
),
),
);
}
}
lib/screen/voucher/sub_widget/voucher_section_title.dart
View file @
c8abf95b
...
...
@@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
class
HeaderSectionTitle
extends
StatelessWidget
{
final
String
title
;
final
VoidCallback
?
onViewAll
;
// 👈 Optional
final
VoidCallback
?
onViewAll
;
const
HeaderSectionTitle
({
super
.
key
,
required
this
.
title
,
this
.
onViewAll
,
// 👈 Nếu null thì không hiển thị button
this
.
onViewAll
,
});
@override
...
...
lib/screen/voucher/voucher_tab_screen.dart
View file @
c8abf95b
...
...
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/voucher_list/voucher_list_screen.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'voucher_tab_viewmodel.dart'
;
import
'sub_widget/voucher_action_menu.dart'
;
import
'sub_widget/voucher_item_grid.dart'
;
...
...
@@ -9,9 +10,16 @@ import 'sub_widget/voucher_item_list.dart';
import
'sub_widget/voucher_section_title.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
class
VoucherTabScreen
extends
State
less
Widget
{
class
VoucherTabScreen
extends
State
ful
Widget
{
const
VoucherTabScreen
({
super
.
key
});
@override
State
<
VoucherTabScreen
>
createState
()
=>
_VoucherTabScreenState
();
}
class
_VoucherTabScreenState
extends
State
<
VoucherTabScreen
>
{
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
@override
Widget
build
(
BuildContext
context
)
{
final
VoucherTabViewModel
viewModel
=
Get
.
put
(
VoucherTabViewModel
());
...
...
@@ -20,6 +28,7 @@ class VoucherTabScreen extends StatelessWidget {
appBar:
CustomNavigationBar
(
title:
"Ưu đãi"
,
showBackButton:
false
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
rightButtons:
[
IconButton
(
icon:
const
Icon
(
Icons
.
search
,
color:
Colors
.
white
),
...
...
lib/shared/router_gage.dart
View file @
c8abf95b
...
...
@@ -3,11 +3,15 @@ import 'package:get/get.dart';
import
'package:mypoint_flutter_app/screen/news/news_list_screen.dart'
;
import
'../screen/achievement/achievement_list_screen.dart'
;
import
'../screen/game/game_cards/game_card_screen.dart'
;
import
'../screen/location_address/location_address_screen.dart'
;
import
'../screen/login/login_screen.dart'
;
import
'../screen/main_tab_screen/main_tab_screen.dart'
;
import
'../screen/membership/membership_screen.dart'
;
import
'../screen/notification/notification_screen.dart'
;
import
'../screen/onboarding/onboarding_screen.dart'
;
import
'../screen/order_menu/order_menu_screen.dart'
;
import
'../screen/pageDetail/campaign_detail_screen.dart'
;
import
'../screen/personal/personal_edit_screen.dart'
;
import
'../screen/register_campaign/register_form_input_screen.dart'
;
import
'../screen/setting/setting_screen.dart'
;
import
'../screen/splash/splash_screen.dart'
;
...
...
@@ -15,6 +19,7 @@ import '../screen/support/support_screen.dart';
import
'../screen/transaction/history/transaction_history_detail_screen.dart'
;
import
'../screen/transaction/transaction_detail_screen.dart'
;
import
'../screen/voucher/detail/voucher_detail_screen.dart'
;
import
'../screen/voucher/my_voucher/my_product_list_widget.dart'
;
import
'../screen/voucher/voucher_list/voucher_list_screen.dart'
;
import
'../screen/vplay_game_center/vplay_game_center_screen.dart'
;
import
'../screen/webview/payment_web_view_screen.dart'
;
...
...
@@ -39,6 +44,11 @@ const campaignDetailScreen = '/campaignDetailScreen';
const
newsListScreen
=
'/newsListScreen'
;
const
achievementListScreen
=
'/achievementListScreen'
;
const
vplayGameCenterScreen
=
'/vplayGameCenterScreen'
;
const
myVoucherListScreen
=
'/myVoucherListScreen'
;
const
personalEditScreen
=
'/personalEditScreen'
;
const
orderMenuScreen
=
'/orderMenuScreen'
;
const
locationAddressScreen
=
'/locationAddressScreen'
;
const
membershipScreen
=
'/membershipScreen'
;
class
RouterPage
{
static
List
<
GetPage
>
pages
()
{
...
...
@@ -68,6 +78,11 @@ class RouterPage {
GetPage
(
name:
newsListScreen
,
page:
()
=>
NewsListScreen
()),
GetPage
(
name:
achievementListScreen
,
page:
()
=>
AchievementListScreen
()),
GetPage
(
name:
vplayGameCenterScreen
,
page:
()
=>
VplayGameCenterScreen
()),
GetPage
(
name:
myVoucherListScreen
,
page:
()
=>
MyVoucherListScreen
()),
GetPage
(
name:
personalEditScreen
,
page:
()
=>
PersonalEditScreen
()),
GetPage
(
name:
orderMenuScreen
,
page:
()
=>
OrderMenuScreen
()),
GetPage
(
name:
locationAddressScreen
,
page:
()
=>
LocationAddressScreen
()),
GetPage
(
name:
membershipScreen
,
page:
()
=>
MembershipScreen
()),
];
}
}
...
...
lib/widgets/back_button.dart
View file @
c8abf95b
...
...
@@ -22,8 +22,8 @@ class CustomBackButton extends StatelessWidget {
children:
[
Center
(
child:
Container
(
height:
2
8
,
width:
2
8
,
height:
2
4
,
width:
2
4
,
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
BaseColor
.
second300
,
width:
1
),
borderRadius:
BorderRadius
.
circular
(
8
),
...
...
@@ -33,17 +33,16 @@ class CustomBackButton extends StatelessWidget {
),
Center
(
child:
IconButton
(
icon:
Icon
(
Icons
.
arrow_back_ios_rounded
,
color:
iconColor
??
BaseColor
.
second600
,
size:
iconSize
??
24
),
icon:
Icon
(
Icons
.
arrow_back_ios_rounded
,
color:
iconColor
??
BaseColor
.
second600
,
size:
iconSize
??
16
),
onPressed:
onPressed
??
()
{
if
(
Get
.
key
.
currentState
?.
canPop
()
==
true
)
{
Get
.
back
();
// if (Navigator.canPop(context)) {
// Navigator.pop(context);
// } else {
// DataPreference.instance.clearData();
// Get.offAllNamed(onboardingScreen);
// }
}
else
{
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
}
},
),
),
...
...
lib/widgets/custom_empty_widget.dart
View file @
c8abf95b
...
...
@@ -3,11 +3,13 @@ import 'package:flutter/material.dart';
class
EmptyWidget
extends
StatelessWidget
{
final
String
imageAsset
;
final
String
content
;
final
Size
size
;
const
EmptyWidget
({
super
.
key
,
this
.
imageAsset
=
'assets/images/ic_pipi_06.png'
,
this
.
content
=
'Không có dữ liệu hiển thị'
,
this
.
size
=
const
Size
(
120
,
120
),
});
@override
...
...
@@ -18,8 +20,8 @@ class EmptyWidget extends StatelessWidget {
children:
[
Image
.
asset
(
imageAsset
,
width:
120
,
height:
120
,
width:
size
.
width
,
height:
size
.
height
,
fit:
BoxFit
.
contain
,
),
const
SizedBox
(
height:
16
),
...
...
lib/widgets/custom_navigation_bar.dart
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'back_button.dart'
;
import
'image_loader.dart'
;
class
CustomNavigationBar
extends
StatelessWidget
implements
PreferredSizeWidget
{
final
String
title
;
...
...
@@ -20,19 +21,32 @@ class CustomNavigationBar extends StatelessWidget implements PreferredSizeWidget
@override
Widget
build
(
BuildContext
context
)
{
final
double
statusBarHeight
=
MediaQuery
.
of
(
context
).
padding
.
top
;
final
bool
isHttp
=
backgroundImage
!=
null
&&
(
backgroundImage
!.
startsWith
(
'http://'
)
||
backgroundImage
!.
startsWith
(
'https://'
));
return
Container
(
height:
statusBarHeight
+
kToolbarHeight
,
decoration:
BoxDecoration
(
image:
backgroundImage
!=
null
?
DecorationImage
(
image:
AssetImage
(
backgroundImage
!),
fit:
BoxFit
.
cover
,
)
:
null
,
//
image: backgroundImage != null
//
? DecorationImage(
//
image: AssetImage(backgroundImage!),
//
fit: BoxFit.cover,
//
)
//
: null,
color:
backgroundImage
==
null
?
Colors
.
white
:
null
,
),
child:
SafeArea
(
child:
Stack
(
fit:
StackFit
.
expand
,
children:
[
if
(
backgroundImage
!=
null
)
isHttp
?
loadNetworkImage
(
url:
backgroundImage
,
fit:
BoxFit
.
cover
,
placeholderAsset:
'assets/images/bg_header_navi.png'
,
)
:
Image
.
asset
(
backgroundImage
!,
fit:
BoxFit
.
cover
),
SafeArea
(
bottom:
false
,
child:
Stack
(
alignment:
Alignment
.
center
,
...
...
@@ -40,31 +54,19 @@ class CustomNavigationBar extends StatelessWidget implements PreferredSizeWidget
// Title ở giữa
Text
(
title
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
,
),
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
),
textAlign:
TextAlign
.
center
,
),
// Back button bên trái
if
(
showBackButton
)
Positioned
(
left:
12
,
child:
CustomBackButton
(),
),
if
(
showBackButton
)
Positioned
(
left:
12
,
child:
CustomBackButton
()),
// Buttons bên phải
if
(
rightButtons
!=
null
)
Positioned
(
right:
12
,
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
rightButtons
!,
Positioned
(
right:
12
,
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
rightButtons
!)),
],
),
),
],
),
),
);
}
}
lib/widgets/time_picker_widget.dart
0 → 100644
View file @
c8abf95b
import
'package:flutter/material.dart'
;
import
'package:intl/intl.dart'
;
class
DatePickerField
extends
StatefulWidget
{
final
String
label
;
final
DateTime
?
initialDate
;
final
Function
(
DateTime
)
onDateSelected
;
final
bool
enabled
;
const
DatePickerField
({
super
.
key
,
required
this
.
label
,
this
.
initialDate
,
required
this
.
onDateSelected
,
this
.
enabled
=
true
,
});
@override
State
<
DatePickerField
>
createState
()
=>
_DatePickerFieldState
();
}
class
_DatePickerFieldState
extends
State
<
DatePickerField
>
{
DateTime
?
_selectedDate
;
@override
void
initState
()
{
super
.
initState
();
_selectedDate
=
widget
.
initialDate
;
}
Future
<
void
>
_pickDate
()
async
{
if
(!
widget
.
enabled
)
return
;
final
now
=
DateTime
.
now
();
final
picked
=
await
showDatePicker
(
context:
context
,
initialDate:
_selectedDate
??
now
,
firstDate:
DateTime
(
1900
),
lastDate:
DateTime
(
2100
),
);
if
(
picked
!=
null
)
{
setState
(()
{
_selectedDate
=
picked
;
});
widget
.
onDateSelected
(
picked
);
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
displayText
=
_selectedDate
!=
null
?
DateFormat
(
'dd/MM/yyyy'
).
format
(
_selectedDate
!)
:
widget
.
label
;
return
GestureDetector
(
onTap:
_pickDate
,
child:
AbsorbPointer
(
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
14
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
grey
.
shade400
),
borderRadius:
BorderRadius
.
circular
(
8
),
color:
widget
.
enabled
?
Colors
.
white
:
Colors
.
grey
.
shade100
,
),
child:
Row
(
children:
[
const
Icon
(
Icons
.
calendar_today
,
color:
Colors
.
blueGrey
,
size:
20
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
Text
(
displayText
,
style:
TextStyle
(
fontSize:
16
,
color:
_selectedDate
!=
null
?
Colors
.
black87
:
Colors
.
grey
.
shade500
,
),
),
),
if
(
widget
.
enabled
)
const
Icon
(
Icons
.
expand_more
,
color:
Colors
.
grey
),
],
),
),
),
);
}
}
pubspec.yaml
View file @
c8abf95b
...
...
@@ -52,6 +52,8 @@ dependencies:
qr_flutter
:
^4.0.0
barcode_widget
:
^2.0.1
infinite_carousel
:
^1.0.3
package_info_plus
:
^4.1.0
dotted_border
:
^2.0.0
game_miniapp
:
path
:
../mini_app/game_miniapp
dev_dependencies
:
...
...
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