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
0b973e61
Commit
0b973e61
authored
Oct 08, 2025
by
DatHV
Browse files
update build web, ui
parent
b7cceccb
Changes
47
Show whitespace changes
Inline
Side-by-side
lib/screen/home/custom_widget/achievement_carousel_widget.dart
View file @
0b973e61
...
@@ -11,13 +11,13 @@ class AchievementCarousel extends StatelessWidget {
...
@@ -11,13 +11,13 @@ class AchievementCarousel extends StatelessWidget {
const
AchievementCarousel
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
const
AchievementCarousel
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
void
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
/
1.6
;
if
(
items
.
isEmpty
)
return
const
SizedBox
.
shrink
();
if
(
items
.
isEmpty
)
return
const
SizedBox
.
shrink
();
return
Column
(
return
Column
(
...
@@ -28,7 +28,7 @@ class AchievementCarousel extends StatelessWidget {
...
@@ -28,7 +28,7 @@ class AchievementCarousel extends StatelessWidget {
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
onViewAll:
sectionConfig
?.
buttonViewAll
?.
directionalScreen
!=
null
?
_handleTapRightButton
:
null
,
),
),
SizedBox
(
SizedBox
(
height:
width
*
180
/
230
/
1.6
,
height:
width
*
180
/
230
,
child:
ListView
.
separated
(
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
...
@@ -53,19 +53,23 @@ class AchievementCard extends StatelessWidget {
...
@@ -53,19 +53,23 @@ class AchievementCard extends StatelessWidget {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
widthItem
=
MediaQuery
.
of
(
context
).
size
.
width
/
1.6
;
final
imageUrl
=
(
achievement
.
images
?.
isNotEmpty
==
true
)
?
achievement
.
images
?.
first
.
imageUrl
:
""
;
final
imageUrl
=
(
achievement
.
images
?.
isNotEmpty
==
true
)
?
achievement
.
images
?.
first
.
imageUrl
:
""
;
return
GestureDetector
(
return
GestureDetector
(
onTap:
onTap
,
onTap:
onTap
,
child:
SizedBox
(
width:
widthItem
,
child:
ClipRRect
(
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderRadius:
BorderRadius
.
circular
(
12
),
child:
loadNetworkImage
(
child:
loadNetworkImage
(
url:
imageUrl
,
url:
imageUrl
,
fit:
BoxFit
.
cover
,
fit:
BoxFit
.
cover
,
width:
280
,
width:
double
.
infinity
,
height:
14
0
,
height:
widthItem
*
180
/
23
0
,
placeholderAsset:
'assets/images/ic_logo.png'
,
placeholderAsset:
'assets/images/ic_logo.png'
,
),
),
),
),
),
);
);
}
}
}
}
lib/screen/home/custom_widget/my_product_carousel_widget.dart
View file @
0b973e61
...
@@ -10,7 +10,7 @@ class MyProductCarouselWidget extends StatelessWidget {
...
@@ -10,7 +10,7 @@ class MyProductCarouselWidget extends StatelessWidget {
final
void
Function
(
MyProductModel
)?
onTap
;
final
void
Function
(
MyProductModel
)?
onTap
;
const
MyProductCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
const
MyProductCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
void
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
}
...
...
lib/screen/home/custom_widget/news_carousel_widget.dart
View file @
0b973e61
...
@@ -11,7 +11,7 @@ class NewsCarouselWidget extends StatelessWidget {
...
@@ -11,7 +11,7 @@ class NewsCarouselWidget extends StatelessWidget {
final
void
Function
(
PageItemModel
)?
onTap
;
final
void
Function
(
PageItemModel
)?
onTap
;
const
NewsCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
const
NewsCarouselWidget
({
super
.
key
,
required
this
.
items
,
this
.
sectionConfig
,
this
.
onTap
});
_handleTapRightButton
()
{
void
_handleTapRightButton
()
{
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
sectionConfig
?.
buttonViewAll
?.
directionalScreen
?.
begin
();
}
}
...
...
lib/screen/otp/delete_account_otp_repository.dart
View file @
0b973e61
...
@@ -7,7 +7,6 @@ import '../../networking/restful_api_viewmodel.dart';
...
@@ -7,7 +7,6 @@ import '../../networking/restful_api_viewmodel.dart';
import
'../../configs/constants.dart'
;
import
'../../configs/constants.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../widgets/custom_toast_message.dart'
;
import
'../../widgets/custom_toast_message.dart'
;
import
'../splash/splash_screen_viewmodel.dart'
;
import
'otp_viewmodel.dart'
;
import
'otp_viewmodel.dart'
;
class
DeleteAccountOtpRepository
extends
RestfulApiViewModel
implements
IOtpRepository
{
class
DeleteAccountOtpRepository
extends
RestfulApiViewModel
implements
IOtpRepository
{
...
...
lib/screen/otp/model/create_otp_response_model.dart
View file @
0b973e61
import
'package:json_annotation/json_annotation.dart'
;
import
'package:json_annotation/json_annotation.dart'
;
import
'../../../base/base_response_model.dart'
;
import
'../../splash/splash_screen_viewmodel.dart'
;
part
'create_otp_response_model.g.dart'
;
part
'create_otp_response_model.g.dart'
;
@JsonSerializable
()
@JsonSerializable
()
...
...
lib/screen/otp/model/otp_verify_response_model.dart
View file @
0b973e61
import
'package:json_annotation/json_annotation.dart'
;
import
'package:json_annotation/json_annotation.dart'
;
import
'../../
splash/splash_screen_view
model.dart'
;
import
'../../
../base/base_response_
model.dart'
;
import
'otp_claim_verify_response_model.dart'
;
import
'otp_claim_verify_response_model.dart'
;
part
'otp_verify_response_model.g.dart'
;
part
'otp_verify_response_model.g.dart'
;
...
...
lib/screen/otp/verify_otp_repository.dart
View file @
0b973e61
...
@@ -7,7 +7,6 @@ import '../../base/base_response_model.dart';
...
@@ -7,7 +7,6 @@ import '../../base/base_response_model.dart';
import
'../../networking/restful_api_viewmodel.dart'
;
import
'../../networking/restful_api_viewmodel.dart'
;
import
'../create_pass/create_pass_screen.dart'
;
import
'../create_pass/create_pass_screen.dart'
;
import
'../create_pass/signup_create_password_repository.dart'
;
import
'../create_pass/signup_create_password_repository.dart'
;
import
'../login/login_screen.dart'
;
import
'model/otp_verify_response_model.dart'
;
import
'model/otp_verify_response_model.dart'
;
import
'otp_viewmodel.dart'
;
import
'otp_viewmodel.dart'
;
...
...
lib/screen/personal/personal_screen.dart
View file @
0b973e61
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/directional/directional_screen.dart'
;
import
'package:mypoint_flutter_app/directional/directional_screen.dart'
;
...
@@ -195,16 +196,14 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
...
@@ -195,16 +196,14 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
}
}
Widget
_buildMenuItems
()
{
Widget
_buildMenuItems
()
{
final
menuItems
=
[
var
menuItems
=
[
{
{
'icon'
:
Icons
.
monetization_on_outlined
,
'icon'
:
Icons
.
monetization_on_outlined
,
'assets'
:
'assets/images/ic_point.png'
,
'assets'
:
'assets/images/ic_point.png'
,
'title'
:
'Săn điểm'
,
'title'
:
'Săn điểm'
,
'type'
:
'APP_SCREEN_POINT_HUNTING'
,
'type'
:
'APP_SCREEN_POINT_HUNTING'
,
},
},
{
'icon'
:
Icons
.
qr_code_2
,
'title'
:
'QR Code'
,
'type'
:
'APP_SCREEN_QR_CODE'
},
{
'icon'
:
Icons
.
check_box_outlined
,
'title'
:
'Check-in nhận quà'
,
'type'
:
'DAILY_CHECKIN'
},
{
'icon'
:
Icons
.
check_box_outlined
,
'title'
:
'Check-in nhận quà'
,
'type'
:
'DAILY_CHECKIN'
},
{
'icon'
:
Icons
.
border_right
,
'title'
:
'Hoá đơn điện'
,
'type'
:
'APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC'
},
// {'icon': Icons.emoji_events_outlined, 'title': 'Bảng xếp hạng', 'type': 'APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC'},
// {'icon': Icons.emoji_events_outlined, 'title': 'Bảng xếp hạng', 'type': 'APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC'},
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
'icon'
:
Icons
.
gif_box_outlined
,
'title'
:
'Ưu đãi của tôi'
,
'type'
:
'APP_SCREEN_MY_PURCHASE_ITEMS'
},
{
{
...
@@ -229,6 +228,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
...
@@ -229,6 +228,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
{
'icon'
:
Icons
.
logout
,
'title'
:
'Đăng xuất'
,
'color'
:
Colors
.
red
[
400
],
'type'
:
'LOGOUT'
},
{
'icon'
:
Icons
.
logout
,
'title'
:
'Đăng xuất'
,
'color'
:
Colors
.
red
[
400
],
'type'
:
'LOGOUT'
},
];
];
if
(!
kIsWeb
)
{
menuItems
.
insertAll
(
2
,
[
{
'icon'
:
Icons
.
qr_code_2
,
'title'
:
'QR Code'
,
'type'
:
'APP_SCREEN_QR_CODE'
,},
{
'icon'
:
Icons
.
border_right
,
'title'
:
'Hoá đơn điện'
,
'type'
:
'APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC'
,},
]
);
}
return
Container
(
return
Container
(
color:
Colors
.
white
,
color:
Colors
.
white
,
child:
Column
(
child:
Column
(
...
...
lib/screen/setting/setting_screen.dart
View file @
0b973e61
// setting_screen.dart
// setting_screen.dart
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/setting/setting_viewmodel.dart'
;
import
'package:mypoint_flutter_app/screen/setting/setting_viewmodel.dart'
;
...
@@ -57,6 +58,7 @@ class _SettingScreenState extends State<SettingScreen> {
...
@@ -57,6 +58,7 @@ class _SettingScreenState extends State<SettingScreen> {
onTap:
()
=>
Get
.
to
(
ChangePassScreen
()),
onTap:
()
=>
Get
.
to
(
ChangePassScreen
()),
),
),
_buildDivider
(),
_buildDivider
(),
if
(!
kIsWeb
)
_buildSettingItem
(
_buildSettingItem
(
icon:
Icons
.
fingerprint
,
icon:
Icons
.
fingerprint
,
title:
'Xác thực sinh trắc học'
,
title:
'Xác thực sinh trắc học'
,
...
@@ -85,6 +87,7 @@ class _SettingScreenState extends State<SettingScreen> {
...
@@ -85,6 +87,7 @@ class _SettingScreenState extends State<SettingScreen> {
},
},
),
),
_buildDivider
(),
_buildDivider
(),
if
(!
kIsWeb
)
_buildSettingItem
(
_buildSettingItem
(
icon:
Icons
.
delete_outline
,
icon:
Icons
.
delete_outline
,
title:
'Xóa tài khoản'
,
title:
'Xóa tài khoản'
,
...
...
lib/screen/splash/splash_screen.dart
View file @
0b973e61
...
@@ -29,7 +29,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
...
@@ -29,7 +29,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
final
list
=
response
.
data
?.
updateRequest
;
final
list
=
response
.
data
?.
updateRequest
;
if
(
list
==
null
||
list
.
isEmpty
)
{
if
(
list
==
null
||
list
.
isEmpty
)
{
_viewModel
.
getUserProfile
();
_viewModel
.
makeDataFollowInitApp
();
return
;
return
;
}
}
var
result
=
response
?.
data
?.
updateRequest
?.
first
;
var
result
=
response
?.
data
?.
updateRequest
?.
first
;
...
@@ -103,7 +103,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
...
@@ -103,7 +103,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
text:
"Để sau"
,
text:
"Để sau"
,
onPressed:
()
{
onPressed:
()
{
Get
.
back
();
Get
.
back
();
_viewModel
.
getUserProfile
();
_viewModel
.
makeDataFollowInitApp
();
},
},
bgColor:
Colors
.
white
,
bgColor:
Colors
.
white
,
textColor:
BaseColor
.
primary500
,
textColor:
BaseColor
.
primary500
,
...
...
lib/screen/splash/splash_screen_viewmodel.dart
View file @
0b973e61
...
@@ -8,6 +8,7 @@ import '../../base/base_response_model.dart';
...
@@ -8,6 +8,7 @@ import '../../base/base_response_model.dart';
import
'../../configs/url_params.dart'
;
import
'../../configs/url_params.dart'
;
import
'../../model/auth/login_token_response_model.dart'
;
import
'../../model/auth/login_token_response_model.dart'
;
import
'../../model/auth/profile_response_model.dart'
;
import
'../../model/auth/profile_response_model.dart'
;
import
'../../web/web_helper_stub.dart'
;
import
'models/update_response_model.dart'
;
import
'models/update_response_model.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../preference/point/point_manager.dart'
;
import
'../../preference/point/point_manager.dart'
;
...
@@ -37,23 +38,19 @@ class SplashScreenViewModel extends RestfulApiViewModel {
...
@@ -37,23 +38,19 @@ class SplashScreenViewModel extends RestfulApiViewModel {
}
}
}
}
Future
<
void
>
getUserProfile
()
async
{
Future
<
void
>
makeDataFollowInitApp
()
async
{
// Không cần loadFromStorage nữa vì token đã được set từ main.dart
// await UrlParams.loadFromStorage();
if
(
DataPreference
.
instance
.
logged
)
{
if
(
DataPreference
.
instance
.
logged
)
{
_freshUserProfile
();
_freshUserProfile
();
return
;
return
;
}
}
final
token
=
UrlParams
.
getTokenForApi
()
??
""
;
final
tokenFormWeb
=
UrlParams
.
getTokenForApi
()
??
""
;
print
(
'🔍 SplashScreen - Token from URL:
$token
'
);
print
(
'🔍 SplashScreen - Token from URL:
$tokenFormWeb
'
);
if
(!
kIsWeb
||
token
.
isEmpty
)
{
if
(
tokenFormWeb
.
isEmpty
)
{
print
(
'❌ No token found, going to onboarding'
);
_directionWhenTokenInvalid
();
Get
.
toNamed
(
onboardingScreen
);
return
;
return
;
}
}
print
(
'✅ Token found, proceeding with login'
);
print
(
'✅ Token found, proceeding with login'
);
LoginTokenResponseModel
tokenModel
=
LoginTokenResponseModel
(
accessToken:
token
);
LoginTokenResponseModel
tokenModel
=
LoginTokenResponseModel
(
accessToken:
token
FormWeb
);
await
DataPreference
.
instance
.
saveLoginToken
(
tokenModel
);
await
DataPreference
.
instance
.
saveLoginToken
(
tokenModel
);
_freshUserProfile
();
_freshUserProfile
();
return
;
return
;
...
@@ -68,10 +65,23 @@ class SplashScreenViewModel extends RestfulApiViewModel {
...
@@ -68,10 +65,23 @@ class SplashScreenViewModel extends RestfulApiViewModel {
_freshDataAndToMainScreen
(
userProfile
);
_freshDataAndToMainScreen
(
userProfile
);
}
else
{
}
else
{
DataPreference
.
instance
.
clearLoginToken
();
DataPreference
.
instance
.
clearLoginToken
();
Get
.
toNamed
(
onboardingScreen
);
_directionWhenTokenInvalid
(
);
}
}
}
}
void
_directionWhenTokenInvalid
()
{
Get
.
toNamed
(
onboardingScreen
);
return
;
if
(
kIsWeb
)
{
print
(
'❌ No token found on web, cannot proceed'
);
webCloseApp
({
'message'
:
'No token found, cannot proceed'
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
Get
.
toNamed
(
onboardingScreen
);
}
}
void
_freshDataAndToMainScreen
(
ProfileResponseModel
userProfile
)
async
{
void
_freshDataAndToMainScreen
(
ProfileResponseModel
userProfile
)
async
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
async
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
async
{
await
DataPreference
.
instance
.
saveUserProfile
(
userProfile
);
await
DataPreference
.
instance
.
saveUserProfile
(
userProfile
);
...
@@ -81,11 +91,3 @@ class SplashScreenViewModel extends RestfulApiViewModel {
...
@@ -81,11 +91,3 @@ class SplashScreenViewModel extends RestfulApiViewModel {
});
});
}
}
}
}
\ No newline at end of file
class
EmptyCodable
{
EmptyCodable
.
fromJson
(
dynamic
json
);
Map
<
String
,
dynamic
>
toJson
()
{
return
{};
}
}
lib/screen/voucher/sub_widget/voucher_item_list.dart
View file @
0b973e61
import
'dart:math'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
...
@@ -41,13 +43,15 @@ class VoucherListItem extends StatelessWidget {
...
@@ -41,13 +43,15 @@ class VoucherListItem extends StatelessWidget {
final
brandName
=
product
.
brand
?.
name
??
''
;
final
brandName
=
product
.
brand
?.
name
??
''
;
final
brandLogo
=
product
.
brand
?.
logo
??
''
;
final
brandLogo
=
product
.
brand
?.
logo
??
''
;
final
String
?
bgImage
=
product
.
banner
?.
url
;
final
String
?
bgImage
=
product
.
banner
?.
url
;
final
widthImage
=
MediaQuery
.
of
(
context
).
size
.
width
/
2
-
32
;
final
heightImage
=
widthImage
*
9
/
16
;
return
Column
(
return
Column
(
children:
[
children:
[
Padding
(
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
child:
SizedBox
(
child:
SizedBox
(
height:
112
,
height:
max
(
heightImage
,
112
)
,
child:
Row
(
child:
Row
(
children:
[
children:
[
ClipRRect
(
ClipRRect
(
...
...
lib/screen/webview/payment_web_view_screen.dart
View file @
0b973e61
...
@@ -97,6 +97,19 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
...
@@ -97,6 +97,19 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
return
;
return
;
}
}
input
=
args
;
input
=
args
;
// Web platform: mở URL trong tab mới và đóng màn hình ngay
if
(
kIsWeb
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
async
{
await
_openUrlInBrowser
();
if
(
mounted
)
{
Get
.
back
();
}
});
return
;
}
// Mobile platform: khởi tạo WebView
_controller
=
_controller
=
WebViewController
()
WebViewController
()
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
...
@@ -120,6 +133,31 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
...
@@ -120,6 +133,31 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
@override
@override
Widget
createBody
()
{
Widget
createBody
()
{
// Web platform: hiển thị loading hoặc empty container
if
(
kIsWeb
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Thanh toán"
,
leftButtons:
[
CustomBackButton
(
onPressed:
()
=>
Get
.
back
(),
),
],
),
body:
const
Center
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
CircularProgressIndicator
(),
SizedBox
(
height:
16
),
Text
(
'Đang mở trang thanh toán...'
),
],
),
),
);
}
// Mobile platform: hiển thị WebView
return
Scaffold
(
return
Scaffold
(
appBar:
CustomNavigationBar
(
appBar:
CustomNavigationBar
(
title:
"Thanh toán"
,
title:
"Thanh toán"
,
...
@@ -189,6 +227,28 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
...
@@ -189,6 +227,28 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
Get
.
snackbar
(
'Thông báo'
,
'Đi tới danh sách hợp đồng điện'
);
// placeholder
Get
.
snackbar
(
'Thông báo'
,
'Đi tới danh sách hợp đồng điện'
);
// placeholder
}
}
/// Mở URL trong browser (web platform)
Future
<
void
>
_openUrlInBrowser
()
async
{
try
{
final
uri
=
Uri
.
parse
(
input
.
url
);
await
launchUrl
(
uri
,
mode:
LaunchMode
.
externalApplication
,
);
}
catch
(
e
)
{
debugPrint
(
'❌ Error opening URL:
$e
'
);
// Fallback: mở trong tab hiện tại
try
{
await
launchUrl
(
Uri
.
parse
(
input
.
url
),
mode:
LaunchMode
.
platformDefault
,
);
}
catch
(
e2
)
{
debugPrint
(
'❌ Error opening URL (fallback):
$e2
'
);
}
}
}
_onBackPressed
()
{
_onBackPressed
()
{
final
dataAlert
=
DataAlertModel
(
final
dataAlert
=
DataAlertModel
(
title:
"Huỷ đơn hàng?"
,
title:
"Huỷ đơn hàng?"
,
...
...
lib/web/close_app_example.dart
0 → 100644
View file @
0b973e61
import
'package:flutter/foundation.dart'
;
import
'web_helper.dart'
;
/// Example usage of closeApp functionality
class
CloseAppExample
{
/// Close app without returning any data
static
void
closeAppSimple
()
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app (simple)...'
);
webCloseApp
();
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
/// Close app with success data
static
void
closeAppWithSuccess
()
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app with success data...'
);
webCloseApp
({
'result'
:
'success'
,
'message'
:
'Operation completed successfully'
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
/// Close app with error data
static
void
closeAppWithError
(
String
errorMessage
)
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app with error data...'
);
webCloseApp
({
'result'
:
'error'
,
'message'
:
errorMessage
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
/// Close app with custom data
static
void
closeAppWithCustomData
(
Map
<
String
,
dynamic
>
customData
)
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app with custom data...'
);
webCloseApp
({
'result'
:
'custom'
,
'data'
:
customData
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
/// Example: Close app after successful payment
static
void
closeAppAfterPayment
({
required
String
transactionId
,
required
double
amount
,
required
String
currency
,
})
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app after payment...'
);
webCloseApp
({
'result'
:
'payment_success'
,
'transactionId'
:
transactionId
,
'amount'
:
amount
,
'currency'
:
currency
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
/// Example: Close app after form submission
static
void
closeAppAfterFormSubmission
({
required
String
formType
,
required
Map
<
String
,
dynamic
>
formData
,
})
{
if
(
kIsWeb
)
{
print
(
'🚪 Closing app after form submission...'
);
webCloseApp
({
'result'
:
'form_submitted'
,
'formType'
:
formType
,
'formData'
:
formData
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
}
else
{
print
(
'⚠️ closeApp only works on web platform'
);
}
}
}
lib/web/web_app_initializer.dart
0 → 100644
View file @
0b973e61
import
'package:flutter/foundation.dart'
;
import
'package:mypoint_flutter_app/configs/url_params.dart'
;
import
'web_helper.dart'
;
/// Web-specific initialization and configuration
class
WebAppInitializer
{
static
final
WebAppInitializer
_instance
=
WebAppInitializer
.
_internal
();
factory
WebAppInitializer
()
=>
_instance
;
WebAppInitializer
.
_internal
();
// Ensure web initialization runs only once per app lifecycle
static
bool
_didInit
=
false
;
// Ensure we only start one polling sequence for SDK data
static
bool
_startedSdkPolling
=
false
;
/// Initialize all web-specific features
static
Future
<
void
>
initialize
()
async
{
if
(!
kIsWeb
)
return
;
if
(
_didInit
)
{
// Prevent re-initialization on hot reload / route changes
return
;
}
_didInit
=
true
;
print
(
'🌐 Initializing web-specific features...'
);
// Handle URL parameters
_handleWebUrlParams
();
// Initialize x-app-sdk
_initializeXAppSDK
();
}
/// Handle URL parameters for web
static
void
_handleWebUrlParams
()
{
print
(
'🔍 Handling web URL parameters...'
);
final
uri
=
Uri
.
base
;
print
(
'🔍 Current URI:
${uri.toString()}
'
);
final
token
=
uri
.
queryParameters
[
'token'
];
final
userId
=
uri
.
queryParameters
[
'userId'
];
print
(
'🔍 Web URL Params: {token:
$token
, user_id:
$userId
}'
);
if
(
token
!=
null
&&
token
.
isNotEmpty
)
{
UrlParams
.
setToken
(
token
);
UrlParams
.
setUserId
(
userId
);
print
(
'✅ Token set from URL:
$token
'
);
print
(
'🔍 UrlParams after set:
${UrlParams.allParams}
'
);
// Clean URL to remove query params
webReplaceUrl
(
'/'
);
}
else
{
print
(
'❌ No token found in URL parameters'
);
}
}
/// Initialize x-app-sdk service
static
void
_initializeXAppSDK
()
{
print
(
'🔍 Initializing x-app-sdk...'
);
// Check if x-app-sdk is available from Super App
final
isSDKAvailable
=
webIsSDKAvailable
();
print
(
'🔍 XAppSDK available from Super App:
$isSDKAvailable
'
);
// Always try to initialize once (no-op on non-web/stub)
webInitializeXAppSDK
().
then
((
_
)
{
print
(
'✅ XAppSDK service initialized'
);
// Only poll for data if SDK is actually available from host
if
(
isSDKAvailable
)
{
if
(
_startedSdkPolling
)
return
;
_startedSdkPolling
=
true
;
// Wait a bit for JavaScript to initialize and then check for data
_checkForAppHostData
(
0
);
// Start with retry count 0
}
else
{
print
(
'ℹ️ XAppSDK not available – skipping polling outside Super App.'
);
print
(
'💡 Tip: Test with URL params, e.g. ?token=test123&userId=user123'
);
}
}).
catchError
((
error
)
{
print
(
'❌ Error initializing XAppSDK:
$error
'
);
});
}
/// Check for app host data with retry mechanism (max 3 retries)
static
void
_checkForAppHostData
(
int
retryCount
)
{
print
(
'🔍 Checking for app host data... (attempt
${retryCount + 1}
/4)'
);
// Wait a bit for JavaScript to initialize
Future
.
delayed
(
const
Duration
(
milliseconds:
1000
),
()
{
final
token
=
webGetAppHostToken
();
final
user
=
webGetAppHostUser
();
final
error
=
webGetAppHostError
();
final
isReady
=
webIsAppHostDataReady
();
print
(
'🔍 Data check result:'
);
print
(
' Token:
${token != null ? '***${token.substring(0, 8)}
...'
:
'null'
}
');
print('
User:
$
{
user
?.
toString
()
??
'null'
}
');
print('
Error:
$
{
error
??
'null'
}
');
print('
Ready:
$isReady
');
if (token != null && token.isNotEmpty) {
print('
✅
Token
from
Super
App:
$
{
token
.
substring
(
0
,
8
)}...
');
UrlParams.setToken(token);
if (user != null) {
print('
✅
User
from
Super
App:
$user
');
UrlParams.setUserId(user['
id
']?.toString());
// Store user data for later use
webStoreAppHostData(token, user);
}
} else if (error != null) {
print('
❌
Error
from
Super
App:
$error
');
} else if (!isReady && retryCount < 3) {
print('
⏳
App
host
data
not
ready
yet
,
will
retry
...
(
$
{
retryCount
+
1
}/
3
)
');
// Retry after a longer delay
Future.delayed(const Duration(milliseconds: 2000), () {
_checkForAppHostData(retryCount + 1);
});
} else if (retryCount >= 3) {
print('
❌
Max
retries
reached
(
3
),
giving
up
on
Super
App
data
');
print('
💡
You
can
test
with
URL
parameters:
?
token
=
test123
&
user
={
"id"
:
"user123"
}
');
} else {
print('
❌
No
token
found
from
Super
App
');
}
});
}
}
lib/web/web_helper.dart
View file @
0b973e61
...
@@ -2,3 +2,5 @@ export 'web_helper_stub.dart'
...
@@ -2,3 +2,5 @@ export 'web_helper_stub.dart'
if
(
dart
.
library
.
html
)
'web_helper_web.dart'
;
if
(
dart
.
library
.
html
)
'web_helper_web.dart'
;
lib/web/web_helper_stub.dart
View file @
0b973e61
...
@@ -8,4 +8,69 @@ void webClearStorage() {
...
@@ -8,4 +8,69 @@ void webClearStorage() {
// no-op on non-web
// no-op on non-web
}
}
/// Get token from app host via x-app-sdk
String
?
webGetAppHostToken
()
{
return
null
;
}
/// Get user info from app host via x-app-sdk
Map
<
String
,
dynamic
>?
webGetAppHostUser
()
{
return
null
;
}
/// Check if app host data is ready
bool
webIsAppHostDataReady
(
)
{
return
false
;
}
/// Get error message from app host
String
?
webGetAppHostError
()
{
return
null
;
}
/// Initialize x-app-sdk service
Future
<
void
>
webInitializeXAppSDK
()
async
{
// no-op on non-web
}
/// Store app host data
void
webStoreAppHostData
(
String
token
,
Map
<
String
,
dynamic
>?
user
)
{
// no-op on non-web
}
/// Clear app host data
void
webClearAppHostData
(
)
{
// no-op on non-web
}
/// Execute JavaScript in the web context
Future
<
dynamic
>
webExecuteJavaScript
(
String
script
)
async
{
return
null
;
}
/// Call x-app-sdk method if available
Future
<
dynamic
>
webCallXAppSDKMethod
(
String
methodName
,
[
List
<
dynamic
>?
args
])
async
{
return
null
;
}
/// Get user info by key from app host
Future
<
dynamic
>
webGetUserInfoByKey
(
String
key
)
async
{
return
null
;
}
/// Get token asynchronously from app host
Future
<
String
?>
webGetTokenAsync
()
async
{
return
null
;
}
/// Check if x-app-sdk is available from Super App
bool
webIsSDKAvailable
(
)
{
return
false
;
}
/// Close app and return to Super App
void
webCloseApp
(
[
Map
<
String
,
dynamic
>?
data
])
{
// no-op on non-web
}
lib/web/web_helper_web.dart
View file @
0b973e61
// Web-specific implementations
// Web-specific implementations
// ignore: avoid_web_libraries_in_flutter
// ignore: avoid_web_libraries_in_flutter
import
'dart:html'
as
html
;
import
'dart:convert'
;
import
'package:universal_html/html.dart'
as
html
;
import
'x_app_sdk_service.dart'
;
void
webReplaceUrl
(
String
path
)
{
void
webReplaceUrl
(
String
path
)
{
try
{
try
{
...
@@ -18,4 +20,134 @@ void webClearStorage() {
...
@@ -18,4 +20,134 @@ void webClearStorage() {
}
catch
(
_
)
{}
}
catch
(
_
)
{}
}
}
/// Get token from app host via x-app-sdk
String
?
webGetAppHostToken
()
{
try
{
return
XAppSDKService
().
getToken
();
}
catch
(
e
)
{
print
(
'❌ Error getting app host token:
$e
'
);
return
null
;
}
}
/// Get user info from app host via x-app-sdk
Map
<
String
,
dynamic
>?
webGetAppHostUser
()
{
try
{
return
XAppSDKService
().
getUser
();
}
catch
(
e
)
{
print
(
'❌ Error getting app host user:
$e
'
);
return
null
;
}
}
/// Check if app host data is ready
bool
webIsAppHostDataReady
(
)
{
try
{
return
XAppSDKService
().
isServiceReady
;
}
catch
(
e
)
{
print
(
'❌ Error checking app host data ready:
$e
'
);
return
false
;
}
}
/// Get error message from app host
String
?
webGetAppHostError
()
{
try
{
return
XAppSDKService
().
getErrorMessage
();
}
catch
(
e
)
{
print
(
'❌ Error getting app host error:
$e
'
);
return
null
;
}
}
/// Initialize x-app-sdk service
Future
<
void
>
webInitializeXAppSDK
()
async
{
try
{
await
XAppSDKService
().
initialize
();
XAppSDKService
().
listenForUpdates
();
}
catch
(
e
)
{
print
(
'❌ Error initializing x-app-sdk:
$e
'
);
}
}
/// Store app host data
void
webStoreAppHostData
(
String
token
,
Map
<
String
,
dynamic
>?
user
)
{
try
{
XAppSDKService
().
storeData
(
token
,
user
);
}
catch
(
e
)
{
print
(
'❌ Error storing app host data:
$e
'
);
}
}
/// Clear app host data
void
webClearAppHostData
(
)
{
try
{
XAppSDKService
().
clearData
();
}
catch
(
e
)
{
print
(
'❌ Error clearing app host data:
$e
'
);
}
}
/// Execute JavaScript in the web context
Future
<
dynamic
>
webExecuteJavaScript
(
String
script
)
async
{
try
{
// For now, we'll use a simpler approach
// This method is mainly for future extensibility
print
(
'⚠️ webExecuteJavaScript is not fully implemented yet'
);
return
null
;
}
catch
(
e
)
{
print
(
'❌ Error executing JavaScript:
$e
'
);
return
null
;
}
}
/// Call x-app-sdk method if available
Future
<
dynamic
>
webCallXAppSDKMethod
(
String
methodName
,
[
List
<
dynamic
>?
args
])
async
{
try
{
return
await
XAppSDKService
().
callSDKMethod
(
methodName
,
args
);
}
catch
(
e
)
{
print
(
'❌ Error calling x-app-sdk method
$methodName
:
$e
'
);
return
null
;
}
}
/// Get user info by key from app host
Future
<
dynamic
>
webGetUserInfoByKey
(
String
key
)
async
{
try
{
return
await
XAppSDKService
().
getUserInfo
(
key
);
}
catch
(
e
)
{
print
(
'❌ Error getting user info by key
$key
:
$e
'
);
return
null
;
}
}
/// Get token asynchronously from app host
Future
<
String
?>
webGetTokenAsync
()
async
{
try
{
return
await
XAppSDKService
().
getTokenAsync
();
}
catch
(
e
)
{
print
(
'❌ Error getting token async:
$e
'
);
return
null
;
}
}
/// Check if x-app-sdk is available from Super App
bool
webIsSDKAvailable
(
)
{
try
{
return
XAppSDKService
().
isSDKAvailable
();
}
catch
(
e
)
{
print
(
'❌ Error checking SDK availability:
$e
'
);
return
false
;
}
}
/// Close app and return to Super App
void
webCloseApp
(
[
Map
<
String
,
dynamic
>?
data
])
{
try
{
XAppSDKService
().
closeApp
(
data
);
}
catch
(
e
)
{
print
(
'❌ Error closing app:
$e
'
);
}
}
lib/web/x_app_sdk_service.dart
0 → 100644
View file @
0b973e61
import
'dart:convert'
;
import
'package:flutter/foundation.dart'
;
import
'package:universal_html/html.dart'
as
html
;
import
'package:universal_html/js_util.dart'
;
class
XAppSDKService
{
static
final
XAppSDKService
_instance
=
XAppSDKService
.
_internal
();
factory
XAppSDKService
()
=>
_instance
;
XAppSDKService
.
_internal
();
String
?
_token
;
Map
<
String
,
dynamic
>?
_user
;
bool
_isReady
=
false
;
String
?
_error
;
String
?
get
token
=>
_token
;
Map
<
String
,
dynamic
>?
get
user
=>
_user
;
bool
get
isReady
=>
_isReady
;
String
?
get
error
=>
_error
;
/// Initialize x-app-sdk service and get data from app host
Future
<
void
>
initialize
()
async
{
if
(!
kIsWeb
)
return
;
try
{
// Wait longer for the JavaScript to initialize
await
Future
.
delayed
(
const
Duration
(
milliseconds:
1000
));
// Check if AppHostData is available in window
final
appHostData
=
getProperty
(
html
.
window
,
'AppHostData'
);
if
(
appHostData
!=
null
)
{
final
data
=
jsonDecode
(
appHostData
.
toString
());
_token
=
data
[
'token'
];
_user
=
data
[
'user'
]
!=
null
?
Map
<
String
,
dynamic
>.
from
(
data
[
'user'
])
:
null
;
_isReady
=
data
[
'isReady'
]
??
false
;
_error
=
data
[
'error'
];
print
(
'✅ XAppSDK Service initialized:'
);
print
(
' Token:
${_token != null ? '***${_token!.substring(_token!.length - 4)}
'
:
'null'
}
');
print('
User:
$
{
_user
?.
toString
()
??
'null'
}
');
print('
Ready:
$_isReady
');
if (_error != null) {
print('
Error:
$_error
');
}
} else {
print('
❌
AppHostData
not
found
in
window
,
trying
fallback
...
');
await _tryFallbackMethod();
}
} catch (e) {
print('
❌
Error
initializing
XAppSDK
Service:
$e
');
await _tryFallbackMethod();
}
}
/// Fallback method to get data from URL parameters or localStorage
Future<void> _tryFallbackMethod() async {
try {
// Try to get from URL parameters first
final uri = Uri.base;
final token = uri.queryParameters['
token
'];
final userStr = uri.queryParameters['
user
'];
if (token != null && token.isNotEmpty) {
_token = token;
if (userStr != null && userStr.isNotEmpty) {
try {
_user = jsonDecode(userStr);
} catch (e) {
print('
❌
Failed
to
parse
user
from
URL:
$e
');
}
}
_isReady = true;
print('
✅
Data
loaded
from
URL
parameters
(
fallback
)
');
return;
}
// Try to get from localStorage
final storedToken = html.window.localStorage['
app_host_token
'];
final storedUser = html.window.localStorage['
app_host_user
'];
if (storedToken != null && storedToken.isNotEmpty) {
_token = storedToken;
if (storedUser != null && storedUser.isNotEmpty) {
try {
_user = jsonDecode(storedUser);
} catch (e) {
print('
❌
Failed
to
parse
user
from
localStorage:
$e
');
}
}
_isReady = true;
print('
✅
Data
loaded
from
localStorage
(
fallback
)
');
} else {
print('
❌
No
data
found
in
URL
parameters
or
localStorage
');
_error = '
No
data
available
from
app
host
';
}
} catch (e) {
print('
❌
Error
in
fallback
method:
$e
');
_error = e.toString();
}
}
/// Get token from app host
String? getToken() {
return _token;
}
/// Get user info from app host
Map<String, dynamic>? getUser() {
return _user;
}
/// Check if service is ready
bool get isServiceReady => _isReady;
/// Get error message if any
String? getErrorMessage() {
return _error;
}
/// Clear stored data
void clearData() {
_token = null;
_user = null;
_isReady = false;
_error = null;
if (kIsWeb) {
try {
html.window.localStorage.remove('
app_host_token
');
html.window.localStorage.remove('
app_host_user
');
} catch (e) {
print('
❌
Error
clearing
localStorage:
$e
');
}
}
}
/// Store data for future use
void storeData(String token, Map<String, dynamic>? user) {
_token = token;
_user = user;
_isReady = true;
_error = null;
if (kIsWeb) {
try {
html.window.localStorage['
app_host_token
'] = token;
if (user != null) {
html.window.localStorage['
app_host_user
'] = jsonEncode(user);
}
} catch (e) {
print('
❌
Error
storing
data:
$e
');
}
}
}
/// Listen for data updates from app host
void listenForUpdates() {
if (!kIsWeb) return;
try {
// Set up a periodic check for updates using js_util
final intervalId = callMethod(html.window, '
setInterval
', [
allowInterop(() {
final appHostData = getProperty(html.window, '
AppHostData
');
if (appHostData != null) {
final data = jsonDecode(appHostData.toString());
final newToken = data['
token
'];
final newUser = data['
user
'];
final newReady = data['
isReady
'] ?? false;
final newError = data['
error
'];
if (newReady && (newToken != _token || newUser != _user)) {
_token = newToken;
_user = newUser != null ? Map<String, dynamic>.from(newUser) : null;
_isReady = newReady;
_error = newError;
print('
🔄
XAppSDK
data
updated
from
app
host
');
}
}
}),
2000 // Check every 2 seconds
]);
// Store interval ID for potential cleanup
print('
✅
Update
listener
set
up
with
interval
ID:
$intervalId
');
} catch (e) {
print('
❌
Error
setting
up
update
listener:
$e
');
}
}
/// Call x-app-sdk method directly from Super App
Future<dynamic> callSDKMethod(String methodName, [List<dynamic>? args]) async {
if (!kIsWeb) return null;
try {
// Check if method is available from Super App
final methodExists = getProperty(html.window, methodName);
if (methodExists == null) {
print('
❌
Method
$methodName
not
available
from
Super
App
');
return null;
}
// Call method directly using callMethod
if (methodName == '
getToken
') {
return await promiseToFuture(callMethod(html.window, '
getToken
', []));
} else if (methodName == '
getInfo
' && args != null && args.isNotEmpty) {
return await promiseToFuture(callMethod(html.window, '
getInfo
', [args[0]]));
} else {
print('
❌
Unsupported
method:
$methodName
');
return null;
}
} catch (e) {
print('
❌
Error
calling
SDK
method
$methodName
:
$e
');
return null;
}
}
/// Get user info by key from Super App
Future<dynamic> getUserInfo(String key) async {
return await callSDKMethod('
getInfo
', [key]);
}
/// Get token from Super App
Future<String?> getTokenAsync() async {
return await callSDKMethod('
getToken
');
}
/// Check if x-app-sdk is available from Super App
bool isSDKAvailable() {
if (!kIsWeb) return false;
try {
final getToken = getProperty(html.window, '
getToken
');
final getInfo = getProperty(html.window, '
getInfo
');
return getToken != null && getInfo != null;
} catch (e) {
return false;
}
}
/// Close app and return to Super App
void closeApp([Map<String, dynamic>? data]) {
if (!kIsWeb) return;
try {
print('
🚪
Closing
app
and
returning
to
Super
App
...
');
if (data != null) {
print('
📤
Data
to
return
:
$data
');
}
// Call the JavaScript closeApp function
callMethod(html.window, '
closeApp
', [data]);
print('
✅
closeApp
called
successfully
');
} catch (e) {
print('
❌
Error
calling
closeApp:
$e
');
// Fallback: try to close the window
try {
html.window.close();
} catch (fallbackError) {
print('
❌
Fallback
close
also
failed:
$fallbackError
');
}
}
}
}
run_dev.sh
0 → 100755
View file @
0b973e61
#!/bin/bash
# Script để chạy development
echo
"🔧 Running Development..."
# Kill server cũ
lsof
-i
:8080 |
awk
'NR>1 {print $2}'
| xargs
kill
-9
2>/dev/null
||
true
# Chuyển sang dev environment
./scripts/switch_env.sh dev
# Chạy web app (sẽ tự mở Chrome)
./scripts/run_web_complete.sh
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