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
1edd930e
Commit
1edd930e
authored
Dec 05, 2025
by
DatHV
Browse files
update profile web.
parent
a7abbe4d
Changes
18
Hide whitespace changes
Inline
Side-by-side
ios/.env_scheme
View file @
1edd930e
pro
stg
lib/app/config/api_paths.dart
View file @
1edd930e
...
...
@@ -125,4 +125,5 @@ class APIPaths {//sandbox
static
const
String
myProductMarkAsUsed
=
"/myProductMarkAsUsed/1.0.0"
;
static
const
String
myProductMarkAsNotUsedYet
=
"/myProductMarkAsNotUsedYet/1.0.0"
;
static
const
String
submitCampaignViewVoucherComplete
=
"/campaign/api/v3.0/view-voucher/complete"
;
static
const
String
webUpdateProfile
=
"/user/api/v2.0/account/users/loginPartner/updateProfile"
;
}
lib/core/network/interceptor/auth_interceptor.dart
View file @
1edd930e
...
...
@@ -21,6 +21,7 @@ class AuthInterceptor extends Interceptor {
APIPaths
.
refreshToken
,
APIPaths
.
logout
,
APIPaths
.
deleteNotification
,
APIPaths
.
webUpdateProfile
,
'assets'
,
];
...
...
lib/core/network/restful_api_client_all_request.dart
View file @
1edd930e
...
...
@@ -700,4 +700,10 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
return
SubmitViewVoucherCompletedResponse
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
EmptyCodable
>>
webUpdateProfile
(
Json
body
)
async
{
return
requestNormal
(
APIPaths
.
webUpdateProfile
,
Method
.
POST
,
body
,
(
data
)
{
return
EmptyCodable
.
fromJson
(
data
as
Json
);
});
}
}
lib/core/services/web/js_interop_stub.dart
0 → 100644
View file @
1edd930e
/// No-op fallbacks for JS interop helpers when building for non-web targets.
F
allowInterop
<
F
extends
Function
>(
F
callback
)
=>
callback
;
dynamic
dartify
(
dynamic
source
)
=>
source
;
lib/core/services/web/js_interop_web.dart
0 → 100644
View file @
1edd930e
import
'package:js/js_util.dart'
as
js_util
;
F
allowInterop
<
F
extends
Function
>(
F
callback
)
=>
js_util
.
allowInterop
(
callback
);
dynamic
dartify
(
dynamic
source
)
=>
js_util
.
dartify
(
source
);
lib/core/services/web/web_helper_stub.dart
View file @
1edd930e
...
...
@@ -105,10 +105,6 @@ Future<dynamic> webPermissionsRequest(dynamic type) async {
return
null
;
}
Future
<
dynamic
>
webPremissionsRequest
(
dynamic
type
)
async
{
return
null
;
}
Future
<
dynamic
>
webSaveStore
(
dynamic
data
)
async
{
return
null
;
}
...
...
@@ -121,6 +117,6 @@ Future<dynamic> webClearStore() async {
return
null
;
}
Future
<
dynamic
>
webGetInfo
(
dynamic
key
)
async
{
Future
<
Map
<
String
,
dynamic
>
?>
webGetInfo
(
dynamic
key
)
async
{
return
null
;
}
lib/core/services/web/web_helper_web.dart
View file @
1edd930e
...
...
@@ -175,9 +175,7 @@ Future<dynamic> webPaymentRequest(Map<String, dynamic> payload) async {
}
}
Future
<
VoidCallback
?>
webListenNotificationEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
Future
<
VoidCallback
?>
webListenNotificationEvent
(
ValueChanged
<
dynamic
>
onEvent
)
async
{
try
{
return
await
XAppSDKService
().
listenNotificationEvent
(
onEvent
);
}
catch
(
e
)
{
...
...
@@ -186,9 +184,7 @@ Future<VoidCallback?> webListenNotificationEvent(
}
}
Future
<
VoidCallback
?>
webListenPaymentEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
Future
<
VoidCallback
?>
webListenPaymentEvent
(
ValueChanged
<
dynamic
>
onEvent
)
async
{
try
{
return
await
XAppSDKService
().
listenPaymentEvent
(
onEvent
);
}
catch
(
e
)
{
...
...
@@ -206,9 +202,6 @@ Future<dynamic> webPermissionsRequest(dynamic type) async {
}
}
Future
<
dynamic
>
webPremissionsRequest
(
dynamic
type
)
=>
webPermissionsRequest
(
type
);
Future
<
dynamic
>
webSaveStore
(
dynamic
data
)
async
{
try
{
return
await
XAppSDKService
().
saveStore
(
data
);
...
...
@@ -236,7 +229,7 @@ Future<dynamic> webClearStore() async {
}
}
Future
<
dynamic
>
webGetInfo
(
dynamic
key
)
async
{
Future
<
Map
<
String
,
dynamic
>
?>
webGetInfo
(
dynamic
key
)
async
{
try
{
return
await
XAppSDKService
().
getInfo
(
key
);
}
catch
(
e
)
{
...
...
lib/core/services/web/web_info_data.dart
0 → 100644
View file @
1edd930e
import
'package:flutter/foundation.dart'
;
import
'package:mypoint_flutter_app/core/services/web/web_helper.dart'
;
class
WebData
{
WebData
.
_internal
();
static
final
WebData
_instance
=
WebData
.
_internal
();
static
WebData
get
instance
=>
_instance
;
static
const
Duration
_sdkTimeout
=
Duration
(
seconds:
10
);
static
const
String
_defaultUserInfoKey
=
'USER_ID'
;
Map
<
String
,
dynamic
>?
_cachedUserInfo
;
/// Convenience static getter for cached data.
static
Map
<
String
,
dynamic
>?
get
cachedUserInfo
=>
_instance
.
_cachedUserInfo
;
/// Return cached info if available, otherwise refresh from SDK.
static
Future
<
Map
<
String
,
dynamic
>?>
getInfoFromSDK
({
bool
forceRefresh
=
false
,
dynamic
key
,
})
{
return
_instance
.
_getInfoFromSDK
(
forceRefresh:
forceRefresh
,
key:
key
);
}
/// Refresh user info data and store it in memory.
Future
<
Map
<
String
,
dynamic
>?>
refreshUserInfo
({
dynamic
key
})
=>
_getInfoFromSDK
(
forceRefresh:
true
,
key:
key
);
/// Retrieve cached data (without hitting SDK).
Map
<
String
,
dynamic
>?
get
userInfo
=>
_cachedUserInfo
;
/// Retrieve cached full name (if available).
String
?
get
_fullName
=>
_extractFullName
(
_cachedUserInfo
);
/// Retrieve cached identity number (if available).
String
?
get
_identityNumber
=>
_extractIdentityNumber
(
_cachedUserInfo
);
/// Retrieve cached email (if available).
String
?
get
_email
=>
_extractEmail
(
_cachedUserInfo
);
/// Retrieve cached gender label/value (if available).
String
?
get
_gender
=>
_extractGender
(
_cachedUserInfo
);
/// Retrieve cached address (if available).
String
?
get
_address
=>
_extractAddress
(
_cachedUserInfo
);
/// Retrieve cached birthday (if available).
String
?
get
_birthday
=>
_extractBirthday
(
_cachedUserInfo
);
/// Retrieve cached avatar (if available).
String
?
get
_avatar
=>
_extractAvatar
(
_cachedUserInfo
);
/// Convenience static getter for cached full name.
static
String
?
getFullName
()
=>
_instance
.
_fullName
;
/// Convenience static getter for cached identity number.
static
String
?
getIdentityNumber
()
=>
_instance
.
_identityNumber
;
/// Convenience static getter for cached email.
static
String
?
getEmail
()
=>
_instance
.
_email
;
/// Convenience static getter for cached gender.
static
String
?
getGender
()
=>
_instance
.
_gender
;
/// Convenience static getter for cached address.
static
String
?
getAddress
()
=>
_instance
.
_address
;
/// Convenience static getter for cached birthday.
static
String
?
getBirthday
()
=>
_instance
.
_birthday
;
/// Convenience static getter for cached avatar.
static
String
?
getAvatar
()
=>
_instance
.
_avatar
;
/// Clear cached SDK data.
void
clearUserInfo
()
{
_cachedUserInfo
=
null
;
}
Future
<
Map
<
String
,
dynamic
>?>
_getInfoFromSDK
({
bool
forceRefresh
=
false
,
dynamic
key
,
})
async
{
if
(!
kIsWeb
)
return
null
;
if
(!
forceRefresh
&&
_cachedUserInfo
!=
null
)
{
return
_cachedUserInfo
;
}
try
{
debugPrint
(
'🔍 WebInfoData - Requesting info from x-app-sdk...'
);
final
ready
=
await
_ensureSdkReady
();
if
(!
ready
)
{
debugPrint
(
'⚠️ WebInfoData - SDK not ready'
);
return
null
;
}
final
response
=
await
webGetInfo
(
key
??
_defaultUserInfoKey
).
timeout
(
_sdkTimeout
);
debugPrint
(
'🔍 WebInfoData - getInfo response:
$response
'
);
if
(
response
==
null
||
response
.
isEmpty
)
{
final
error
=
webGetLastError
();
debugPrint
(
'⚠️ WebInfoData - getInfo returned empty payload:
$error
'
);
_cachedUserInfo
=
null
;
return
null
;
}
final
status
=
response
[
'status'
]?.
toString
().
toLowerCase
();
if
(
status
!=
null
&&
status
!=
'success'
)
{
debugPrint
(
'⚠️ WebInfoData - getInfo returned non-success status:
$status
'
);
_cachedUserInfo
=
null
;
return
null
;
}
final
userInfo
=
_mapFromDynamic
(
response
[
'data'
]);
if
(
userInfo
!=
null
&&
userInfo
.
isNotEmpty
)
{
_cachedUserInfo
=
userInfo
;
debugPrint
(
'✅ WebInfoData - User info cached from x-app-sdk'
);
return
_cachedUserInfo
;
}
debugPrint
(
'⚠️ WebInfoData - getInfo returned missing profile data'
);
_cachedUserInfo
=
null
;
}
catch
(
e
)
{
debugPrint
(
'❌ WebInfoData - Error getting profile from SDK:
$e
'
);
_cachedUserInfo
=
null
;
}
return
null
;
}
static
Future
<
bool
>
_ensureSdkReady
()
async
{
try
{
await
webInitializeXAppSDK
().
timeout
(
_sdkTimeout
);
}
catch
(
e
)
{
debugPrint
(
'⚠️ WebInfoData - SDK init attempt failed:
$e
'
);
}
final
ready
=
webIsSDKInitialized
();
return
ready
;
}
static
Map
<
String
,
dynamic
>?
_mapFromDynamic
(
dynamic
source
)
{
if
(
source
==
null
)
return
null
;
if
(
source
is
Map
<
String
,
dynamic
>)
{
return
source
.
map
((
key
,
value
)
=>
MapEntry
(
key
,
_normalizeDynamicValue
(
value
)));
}
if
(
source
is
Map
)
{
final
mapped
=
<
String
,
dynamic
>{};
source
.
forEach
((
key
,
dynamic
value
)
{
if
(
key
==
null
)
{
return
;
}
mapped
[
key
.
toString
()]
=
_normalizeDynamicValue
(
value
);
});
return
mapped
;
}
return
null
;
}
static
dynamic
_normalizeDynamicValue
(
dynamic
value
)
{
if
(
value
is
Map
)
{
return
_mapFromDynamic
(
value
);
}
if
(
value
is
Iterable
)
{
return
List
<
dynamic
>.
from
(
value
.
map
(
_normalizeDynamicValue
));
}
return
value
;
}
String
?
_extractFullName
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
final
possibleKeys
=
<
String
>[
'Full_Name'
,
'FullName'
,
'fullName'
,
'fullname'
,
'full_name'
,
'name'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedName
=
_extractFullName
(
_mapFromDynamic
(
workerSite
));
if
(
nestedName
!=
null
&&
nestedName
.
isNotEmpty
)
{
return
nestedName
;
}
}
return
null
;
}
String
?
_extractIdentityNumber
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'identityNumber'
,
'identity_number'
,
'identificationNumber'
,
'identification_number'
,
'idNumber'
,
'id_number'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedIdentity
=
_extractIdentityNumber
(
_mapFromDynamic
(
workerSite
));
if
(
nestedIdentity
!=
null
&&
nestedIdentity
.
isNotEmpty
)
{
return
nestedIdentity
;
}
}
return
null
;
}
String
?
_extractEmail
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'email'
,
'Email'
,
'userEmail'
,
'user_email'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedEmail
=
_extractEmail
(
_mapFromDynamic
(
workerSite
));
if
(
nestedEmail
!=
null
&&
nestedEmail
.
isNotEmpty
)
{
return
nestedEmail
;
}
}
return
null
;
}
String
?
_extractGender
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'gender'
,
'Gender'
,
'sex'
,
'sexLabel'
,
'sex_label'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
==
null
)
{
continue
;
}
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
if
(
value
is
num
)
{
return
value
.
toString
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedGender
=
_extractGender
(
_mapFromDynamic
(
workerSite
));
if
(
nestedGender
!=
null
&&
nestedGender
.
isNotEmpty
)
{
return
nestedGender
;
}
}
return
null
;
}
String
?
_extractAddress
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'address'
,
'Address'
,
'addressFull'
,
'address_full'
,
'permanentAddress'
,
'permanent_address'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedAddress
=
_extractAddress
(
_mapFromDynamic
(
workerSite
));
if
(
nestedAddress
!=
null
&&
nestedAddress
.
isNotEmpty
)
{
return
nestedAddress
;
}
}
return
null
;
}
String
?
_extractBirthday
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'birthday'
,
'birthDay'
,
'birth_day'
,
'dob'
,
'dateOfBirth'
,
'date_of_birth'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedBirthday
=
_extractBirthday
(
_mapFromDynamic
(
workerSite
));
if
(
nestedBirthday
!=
null
&&
nestedBirthday
.
isNotEmpty
)
{
return
nestedBirthday
;
}
}
return
null
;
}
String
?
_extractAvatar
(
Map
<
String
,
dynamic
>?
data
)
{
if
(
data
==
null
||
data
.
isEmpty
)
{
return
null
;
}
const
possibleKeys
=
<
String
>[
'avatar'
,
'avatarUrl'
,
'avatarURL'
,
'avatar_url'
,
'avatar2'
,
'avatar_2'
,
'profileImage'
,
'profile_image'
,
];
for
(
final
key
in
possibleKeys
)
{
final
value
=
data
[
key
];
if
(
value
is
String
&&
value
.
trim
().
isNotEmpty
)
{
return
value
.
trim
();
}
}
final
workerSite
=
data
[
'worker_site'
];
if
(
workerSite
is
Map
)
{
final
nestedAvatar
=
_extractAvatar
(
_mapFromDynamic
(
workerSite
));
if
(
nestedAvatar
!=
null
&&
nestedAvatar
.
isNotEmpty
)
{
return
nestedAvatar
;
}
}
final
workingSite
=
data
[
'working_site'
];
if
(
workingSite
is
Map
)
{
final
nestedAvatar
=
_extractAvatar
(
_mapFromDynamic
(
workingSite
));
if
(
nestedAvatar
!=
null
&&
nestedAvatar
.
isNotEmpty
)
{
return
nestedAvatar
;
}
}
return
null
;
}
}
lib/core/services/web/x_app_sdk_service.dart
View file @
1edd930e
import
'package:flutter/foundation.dart'
;
import
'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart'
;
import
'package:universal_html/html.dart'
as
html
;
import
'package:universal_html/js_util.dart'
;
import
'js_interop_stub.dart'
if
(
dart
.
library
.
html
)
'js_interop_web.dart'
as
js_interop
;
/// X-App-SDK Service for Flutter Web
/// Provides integration with x-app-sdk module and exposes supported APIs to Dart
class
XAppSDKService
{
...
...
@@ -16,6 +20,14 @@ class XAppSDKService {
final
Set
<
dynamic
>
_listenerDisposers
=
<
dynamic
>{};
bool
_browserMode
=
false
;
static
bool
isBrowserMode
()
=>
_instance
.
_browserMode
;
static
const
Map
<
String
,
String
>
_infoKeyPropertyMap
=
<
String
,
String
>{
'user'
:
'USER'
,
'userid'
:
'USER'
,
'user_id'
:
'USER'
,
};
/// Check if SDK is available and initialized
bool
get
isInitialized
=>
_isInitialized
;
...
...
@@ -58,32 +70,25 @@ class XAppSDKService {
if
(!
await
_ensureSdkReady
())
{
return
null
;
}
debugPrint
(
'🔍 XAppSDKService: Getting token...'
);
try
{
final
result
=
await
_invokeSdkMethod
(
'getToken'
,
ensureInitialized:
false
,
);
final
result
=
await
_invokeSdkMethod
(
'getToken'
,
ensureInitialized:
false
);
if
(
result
!=
null
)
{
final
status
=
getProperty
(
result
,
'status'
)?.
toString
();
final
data
=
getProperty
(
result
,
'data'
);
final
message
=
getProperty
(
result
,
'message'
);
final
errorMessage
=
message
?.
toString
();
if
(
status
==
'success'
&&
data
!=
null
&&
data
.
toString
().
isNotEmpty
)
{
final
tokenString
=
data
.
toString
();
final
tokenString
=
data
?.
toString
().
orEmpty
??
''
;
final
isPlaceholder
=
tokenString
.
trim
().
toLowerCase
().
contains
(
"tokenex.tokenex"
);
print
(
'🔍 XAppSDKService: getToken result - status:
$status
, token length:
${tokenString}
, isPlaceholder:
$isPlaceholder
'
);
if
(
status
==
'success'
&&
data
!=
null
&&
tokenString
.
isNotEmpty
&&
!
isPlaceholder
)
{
_cachedToken
=
tokenString
;
_lastError
=
null
;
final
preview
=
tokenString
.
length
>
8
?
tokenString
.
substring
(
0
,
8
)
:
tokenString
;
debugPrint
(
'✅ XAppSDKService: Token retrieved successfully:
$preview
...'
);
final
preview
=
tokenString
.
length
>
8
?
tokenString
.
substring
(
0
,
8
)
:
tokenString
;
debugPrint
(
'✅ XAppSDKService: Token retrieved successfully:
$preview
...'
);
return
_cachedToken
;
}
else
{
final
details
=
errorMessage
?.
isNotEmpty
==
true
?
' -
$errorMessage
'
:
''
;
final
details
=
errorMessage
?.
isNotEmpty
==
true
?
' -
$errorMessage
'
:
''
;
_lastError
=
'SDK returned status:
$status$details
'
;
debugPrint
(
'❌ XAppSDKService:
$_lastError
'
);
}
...
...
@@ -95,7 +100,6 @@ class XAppSDKService {
_lastError
=
'Error getting token:
$e
'
;
debugPrint
(
'❌ XAppSDKService:
$_lastError
'
);
}
return
null
;
}
...
...
@@ -179,7 +183,7 @@ class XAppSDKService {
final
disposer
=
await
_invokeAfterEnsure
(
'listenNotifiactionEvent'
,
args:
<
dynamic
>[
allowInterop
((
dynamic
event
)
{
js_interop
.
allowInterop
((
dynamic
event
)
{
try
{
onEvent
(
event
);
}
catch
(
error
,
stackTrace
)
{
...
...
@@ -201,7 +205,7 @@ class XAppSDKService {
final
disposer
=
await
_invokeAfterEnsure
(
'listenPaymentEvent'
,
args:
<
dynamic
>[
allowInterop
((
dynamic
event
)
{
js_interop
.
allowInterop
((
dynamic
event
)
{
try
{
onEvent
(
event
);
}
catch
(
error
,
stackTrace
)
{
...
...
@@ -233,9 +237,47 @@ class XAppSDKService {
/// Clear Super App store data
Future
<
dynamic
>
clearStore
()
=>
_invokeAfterEnsure
(
'clearStore'
);
/// Get user info
Future
<
dynamic
>
getInfo
(
dynamic
key
)
=>
_invokeAfterEnsure
(
'getInfo'
,
args:
<
dynamic
>[
key
]);
/// Get user info via SDK (returns the raw response map)
Future
<
Map
<
String
,
dynamic
>?>
getInfo
(
dynamic
key
)
async
{
if
(!
await
_ensureSdkReady
())
{
return
null
;
}
debugPrint
(
'🔍 XAppSDKService: Getting info for key:
$key
'
);
try
{
final
resolvedKey
=
await
_resolveInfoKeyValue
(
key
);
final
result
=
await
_invokeSdkMethod
(
'getInfo'
,
args:
<
dynamic
>[
resolvedKey
??
key
],
ensureInitialized:
false
,
);
if
(
result
==
null
)
{
_lastError
??=
'getInfo returned null'
;
debugPrint
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
var
infoMap
=
_convertDynamicToMap
(
result
);
if
(
infoMap
==
null
||
infoMap
.
isEmpty
)
{
_lastError
=
'getInfo returned invalid data'
;
debugPrint
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
if
(
_browserMode
)
{
infoMap
=
_sanitizeBrowserInfoData
(
infoMap
);
debugPrint
(
'ℹ️ XAppSDKService: Browser mode detected - returning empty info data'
);
}
_lastError
=
null
;
debugPrint
(
'✅ XAppSDKService: Info retrieved for
$key
'
);
return
infoMap
;
}
catch
(
e
)
{
_lastError
=
'Error getting info:
$e
'
;
debugPrint
(
'❌ XAppSDKService:
$_lastError
'
);
}
return
null
;
}
/// Clear cached token
void
clearToken
()
{
...
...
@@ -529,4 +571,96 @@ class XAppSDKService {
}
catch
(
_
)
{}
return
false
;
}
Future
<
dynamic
>
_resolveInfoKeyValue
(
dynamic
key
)
async
{
if
(
key
==
null
)
{
return
null
;
}
if
(
key
is
!
String
)
{
return
key
;
}
final
normalized
=
key
.
trim
();
if
(
normalized
.
isEmpty
)
{
return
normalized
;
}
final
candidates
=
<
String
>[];
final
lower
=
normalized
.
toLowerCase
();
candidates
.
add
(
normalized
);
final
upper
=
normalized
.
toUpperCase
();
if
(
upper
!=
normalized
)
{
candidates
.
add
(
upper
);
}
if
(
lower
!=
normalized
&&
lower
!=
upper
)
{
candidates
.
add
(
lower
);
}
final
aliasProperty
=
_infoKeyPropertyMap
[
lower
];
if
(
aliasProperty
!=
null
&&
!
candidates
.
contains
(
aliasProperty
))
{
candidates
.
add
(
aliasProperty
);
}
final
module
=
await
_loadSdkModule
();
if
(
module
!=
null
)
{
try
{
final
infoEnum
=
getProperty
(
module
,
'EKeyInfor'
);
if
(
infoEnum
!=
null
)
{
for
(
final
candidate
in
candidates
)
{
try
{
final
value
=
getProperty
(
infoEnum
,
candidate
);
if
(
value
!=
null
)
{
return
value
;
}
}
catch
(
_
)
{}
}
}
}
catch
(
_
)
{}
}
return
normalized
;
}
Map
<
String
,
dynamic
>?
_convertDynamicToMap
(
dynamic
source
)
{
if
(
source
==
null
)
{
return
null
;
}
if
(
source
is
Map
<
String
,
dynamic
>)
{
return
source
.
map
((
key
,
value
)
=>
MapEntry
(
key
,
_normalizeDynamicValue
(
value
)));
}
if
(
source
is
Map
)
{
final
normalized
=
<
String
,
dynamic
>{};
source
.
forEach
((
key
,
dynamic
value
)
{
if
(
key
==
null
)
{
return
;
}
normalized
[
key
.
toString
()]
=
_normalizeDynamicValue
(
value
);
});
return
normalized
;
}
try
{
final
dartValue
=
js_interop
.
dartify
(
source
);
if
(
dartValue
is
Map
)
{
return
_convertDynamicToMap
(
dartValue
);
}
}
catch
(
_
)
{}
return
null
;
}
dynamic
_normalizeDynamicValue
(
dynamic
value
)
{
if
(
value
is
Map
)
{
return
_convertDynamicToMap
(
value
);
}
if
(
value
is
Iterable
)
{
return
List
<
dynamic
>.
from
(
value
.
map
(
_normalizeDynamicValue
));
}
return
value
;
}
Map
<
String
,
dynamic
>
_sanitizeBrowserInfoData
(
Map
<
String
,
dynamic
>
payload
)
{
final
sanitized
=
Map
<
String
,
dynamic
>.
from
(
payload
);
if
(
sanitized
.
containsKey
(
'data'
))
{
sanitized
[
'data'
]
=
<
String
,
dynamic
>{};
return
sanitized
;
}
return
<
String
,
dynamic
>{};
}
}
lib/features/home/custom_widget/header_home_widget.dart
View file @
1edd930e
...
...
@@ -66,9 +66,12 @@ class HomeGreetingHeader extends StatelessWidget {
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
'Xin chào
$name
!'
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
),
GestureDetector
(
onTap:
_onProfileTap
,
child:
Text
(
'Xin chào
$name
!'
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black87
),
),
),
Row
(
children:
[
...
...
@@ -152,6 +155,10 @@ class HomeGreetingHeader extends StatelessWidget {
);
}
void
_onProfileTap
()
{
Get
.
toNamed
(
personalEditScreen
);
}
void
_onSearchTap
()
{
Get
.
toNamed
(
vouchersScreen
,
arguments:
{
"enableSearch"
:
true
});
}
...
...
lib/features/home/custom_widget/hover_view_widget.dart
View file @
1edd930e
...
...
@@ -65,7 +65,6 @@ class _HoverViewState extends State<HoverView> {
if
(!
mounted
)
return
;
setState
(()
{
_remainingSeconds
.
value
--;
debugPrint
(
"Remaining seconds:
${_remainingSeconds.value}
"
);
if
(
_remainingSeconds
.
value
<=
0
)
_timer
?.
cancel
();
});
});
...
...
lib/features/personal/personal_edit_screen.dart
View file @
1edd930e
...
...
@@ -4,6 +4,8 @@ import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import
'package:mypoint_flutter_app/features/personal/personal_edit_item_model.dart'
;
import
'package:mypoint_flutter_app/features/personal/personal_edit_viewmodel.dart'
;
import
'package:mypoint_flutter_app/features/personal/personal_gender.dart'
;
import
'package:mypoint_flutter_app/shared/widgets/image_loader.dart'
;
import
'../../core/services/web/web_info_data.dart'
;
import
'../../shared/widgets/base_view/base_screen.dart'
;
import
'../../shared/widgets/base_view/basic_state.dart'
;
import
'../../core/theme/base_color.dart'
;
...
...
@@ -116,11 +118,20 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
}
Widget
_buildAvatarItem
()
{
final
avatar
=
WebData
.
getAvatar
();
return
Center
(
child:
Stack
(
alignment:
Alignment
.
bottomRight
,
children:
[
ClipOval
(
child:
Image
.
asset
(
"assets/images/bg_default_11.png"
,
width:
100
,
height:
100
,
fit:
BoxFit
.
cover
)),
ClipOval
(
child:
loadNetworkImage
(
url:
avatar
,
width:
100
,
height:
100
,
fit:
BoxFit
.
cover
,
placeholderAsset:
"assets/images/bg_default_11.png"
)
),
Positioned
(
bottom:
4
,
right:
4
,
...
...
lib/features/personal/personal_edit_viewmodel.dart
View file @
1edd930e
...
...
@@ -5,6 +5,7 @@ import 'package:mypoint_flutter_app/features/personal/personal_edit_item_model.d
import
'package:mypoint_flutter_app/features/personal/personal_gender.dart'
;
import
'../../core/network/restful_api_viewmodel.dart'
;
import
'../../app/config/constants.dart'
;
import
'../../core/services/web/web_info_data.dart'
;
import
'../../shared/preferences/data_preference.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../core/utils/validation_utils.dart'
;
...
...
@@ -35,17 +36,28 @@ class PersonalEditViewModel extends RestfulApiViewModel {
);
birthday
=
profile
.
workerSite
?.
birthday
?.
toDateFormat
(
'yyyy-MM-dd'
);
gender
=
PersonalGender
.
from
(
profile
.
workerSite
?.
sex
??
"U"
);
// var webGender = WebData.getGender().orEmpty;
// if (webGender.isNotEmpty) {
// gender = PersonalGender.fromInt(webGender);
// }
// var name = WebData.getFullName() ?? profile.workerSite?.fullname ?? "";
// var email = WebData.getEmail() ?? profile.workerSite?.email;
// var address = WebData.getAddress() ?? profile.workerSite?.addressFull;
// var identification = WebData.getIdentityNumber() ?? profile.workerSite?.identificationNumber;
var
name
=
profile
.
workerSite
?.
fullname
??
""
;
var
email
=
profile
.
workerSite
?.
email
;
var
address
=
profile
.
workerSite
?.
addressFull
;
var
identification
=
profile
.
workerSite
?.
identificationNumber
;
editDataModel
.
value
=
PersonalEditDataModel
(
name:
name
,
nickname:
profile
.
workerSite
?.
nickname
,
phone:
profile
.
workerSite
?.
phoneNumber
,
email:
profile
.
workerSite
?.
email
,
identificationNumber:
profile
.
workerSite
?.
identification
Number
,
email:
email
,
identificationNumber:
identification
,
birthday:
birthday
,
gender:
gender
,
address:
profile
.
workerSite
?.
address
Full
,
address:
address
,
province:
province
.
value
,
district:
district
.
value
,
);
...
...
lib/features/personal/personal_gender.dart
View file @
1edd930e
...
...
@@ -14,6 +14,17 @@ enum PersonalGender {
}
}
static
PersonalGender
fromInt
(
String
gender
)
{
switch
(
gender
)
{
case
'1'
:
return
PersonalGender
.
female
;
case
'0'
:
return
PersonalGender
.
male
;
default
:
return
PersonalGender
.
unknown
;
}
}
String
get
value
{
switch
(
this
)
{
case
PersonalGender
.
female
:
...
...
lib/features/personal/personal_screen.dart
View file @
1edd930e
...
...
@@ -6,6 +6,7 @@ import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import
'package:mypoint_flutter_app/shared/preferences/data_preference.dart'
;
import
'package:mypoint_flutter_app/shared/preferences/point/point_manager.dart'
;
import
'../../app/config/constants.dart'
;
import
'../../core/services/web/web_info_data.dart'
;
import
'../../shared/widgets/base_view/base_screen.dart'
;
import
'../../shared/widgets/base_view/basic_state.dart'
;
import
'../../app/routing/directional_action_type.dart'
;
...
...
@@ -14,6 +15,7 @@ import '../../core/theme/base_color.dart';
import
'../../shared/router_gage.dart'
;
import
'../../core/services/logout_service.dart'
;
import
'../../shared/widgets/alert/data_alert_model.dart'
;
import
'../../shared/widgets/image_loader.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../home/models/header_home_model.dart'
;
import
'../popup_manager/popup_runner_helper.dart'
;
...
...
@@ -78,6 +80,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
final
level
=
DataPreference
.
instance
.
rankName
??
"Hạng Đồng"
;
final
email
=
DataPreference
.
instance
.
profile
?.
workerSite
?.
email
??
""
;
final
topWebPadding
=
Constants
.
extendTopPaddingNavigation
;
final
avatar
=
WebData
.
getAvatar
();
return
Container
(
height:
width
*
163
/
375
+
topWebPadding
,
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
NetworkImage
(
data
.
background
??
""
),
fit:
BoxFit
.
cover
)),
...
...
@@ -102,7 +105,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
shape:
BoxShape
.
circle
,
border:
Border
.
all
(
color:
Colors
.
white
,
width:
2
),
),
child:
ClipOval
(
child:
Image
.
asset
(
"assets/images/ic_logo.png"
,
width:
64
,
height:
64
)),
child:
ClipOval
(
child:
loadNetworkImage
(
url:
avatar
,
fit:
BoxFit
.
cover
,
placeholderAsset:
"assets/images/ic_logo.png"
)
),
),
const
SizedBox
(
width:
8
),
Column
(
...
...
lib/features/splash/splash_screen_viewmodel.dart
View file @
1edd930e
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/app/routing/app_navigator.dart'
;
import
'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart'
;
import
'package:mypoint_flutter_app/core/network/restful_api_viewmodel.dart'
;
import
'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart'
;
import
'package:mypoint_flutter_app/features/register_campaign/model/registration_form_package_model.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'../../core/services/web/web_info_data.dart'
;
import
'../../core/services/web/x_app_sdk_service.dart'
;
import
'../login/model/login_token_response_model.dart'
;
import
'../personal/model/profile_response_model.dart'
;
import
'models/update_response_model.dart'
;
...
...
@@ -21,6 +25,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
static
const
Duration
_sdkTimeout
=
Duration
(
seconds:
10
);
Future
<
void
>
checkUpdateApp
()
async
{
debugPrint
(
'🔍 SplashScreen - Checking for app update...
${DateTime.now().toString()}
'
);
if
(
kIsWeb
)
{
checkUpdateResponse
?.
call
(
null
);
return
;
...
...
@@ -33,7 +38,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
debugPrint
(
'⚠️ SplashScreen - checkUpdateApp failed:
$e
'
);
checkUpdateResponse
?.
call
(
null
);
}
}
}
Future
<
void
>
openLink
()
async
{
if
(
_updateLink
.
isEmpty
)
return
;
...
...
@@ -49,6 +54,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
final
token
=
await
_getTokenFromSDK
();
if
(
token
.
orEmpty
.
isNotEmpty
)
{
await
DataPreference
.
instance
.
saveLoginToken
(
LoginTokenResponseModel
(
accessToken:
token
));
await
_cacheProfileFromSDK
();
}
}
if
(
DataPreference
.
instance
.
logged
)
{
...
...
@@ -92,6 +98,22 @@ class SplashScreenViewModel extends RestfulApiViewModel {
}
}
Future
<
void
>
_cacheProfileFromSDK
()
async
{
if
(!
kIsWeb
)
return
;
try
{
final
sdkProfile
=
await
WebData
.
getInfoFromSDK
(
forceRefresh:
true
);
if
(
sdkProfile
==
null
||
sdkProfile
.
isEmpty
)
{
debugPrint
(
'⚠️ SplashScreen - No profile data from x-app-sdk'
);
return
;
}
debugPrint
(
'✅ SplashScreen - Cached user profile from x-app-sdk:
$sdkProfile
'
);
final
response
=
client
.
webUpdateProfile
({
'metadata'
:
sdkProfile
.
toJsonString
()});
debugPrint
(
'🔍 SplashScreen - webUpdateProfile response:
${response.toString()}
'
);
}
catch
(
e
)
{
debugPrint
(
'⚠️ SplashScreen - Failed to cache SDK profile:
$e
'
);
}
}
Future
<
void
>
_loadProfileAndNavigate
()
async
{
final
profile
=
await
_fetchUserProfile
();
if
(
profile
==
null
)
{
...
...
@@ -104,14 +126,27 @@ class SplashScreenViewModel extends RestfulApiViewModel {
}
Future
<
void
>
directionWhenTokenInvalid
()
async
{
// if (kIsWeb) {
// print('❌ No token found on web, closing app');
// final closeSuccess = await webCloseApp({
// 'message': 'No token found, cannot proceed',
// 'timestamp': DateTime.now().millisecondsSinceEpoch,
// });
// if (closeSuccess) return;
// }
if
(
kIsWeb
&&
!
XAppSDKService
.
isBrowserMode
())
{
debugPrint
(
'❌ No token found on web platform. directionWhenTokenInvalid'
);
Future
.
microtask
(()
{
AppNavigator
.
showAlertError
(
content:
"MyPoint cần thông tin của bạn.
\n
Số điện thoại được sử dụng để định danh và sử dụng các tính năng trên MyPoint."
,
onConfirmed:
()
async
{
final
closeSuccess
=
await
webCloseApp
({
'message'
:
'User acknowledged missing token'
,
'timestamp'
:
DateTime
.
now
()
.
millisecondsSinceEpoch
,
});
debugPrint
(
'❌ No token found on web, user acknowledged. Close app success:
$closeSuccess
'
);
if
(!
closeSuccess
)
{
Get
.
offAllNamed
(
onboardingScreen
);
}
}
);
});
return
;
}
final
phone
=
DataPreference
.
instance
.
phoneNumberUsedForLoginScreen
;
final
displayName
=
DataPreference
.
instance
.
displayName
;
if
(
phone
.
isEmpty
)
{
...
...
lib/shared/preferences/data_preference.dart
View file @
1edd930e
import
'dart:convert'
;
import
'package:flutter/foundation.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'../../core/services/web/web_info_data.dart'
;
import
'../../features/login/model/login_token_response_model.dart'
;
import
'../../features/personal/model/profile_response_model.dart'
;
import
'../../features/popup_manager/popup_manager_viewmodel.dart'
;
...
...
@@ -46,7 +47,11 @@ class DataPreference {
String
get
displayName
{
var
name
=
_profile
?.
workerSite
?.
fullname
??
""
;
if
(
name
.
isEmpty
)
{
name
=
"Quý Khách"
;
if
(
kIsWeb
)
{
name
=
WebData
.
getFullName
()
??
"Quý Khách"
;
}
else
{
name
=
"Quý Khách"
;
}
}
return
name
;
}
...
...
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