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
e9cf8244
"ios/Flutter/Pro.xcconfig" did not exist on "36ac8d24573a2935dfb9f560f19ff53c6e9c76bd"
Commit
e9cf8244
authored
Oct 28, 2025
by
DatHV
Browse files
update fix bug, handle error.
parent
a0bcdab2
Changes
104
Show whitespace changes
Inline
Side-by-side
lib/screen/webview/web_view_screen.dart
View file @
e9cf8244
...
...
@@ -10,13 +10,19 @@ import '../../base/base_screen.dart';
import
'../../base/basic_state.dart'
;
import
'../../directional/directional_screen.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../preference/package_info.dart'
;
class
BaseWebViewInput
{
final
String
?
title
;
final
String
url
;
final
bool
isFullScreen
;
const
BaseWebViewInput
({
this
.
title
,
required
this
.
url
,
this
.
isFullScreen
=
false
});
const
BaseWebViewInput
({
this
.
title
,
required
this
.
url
,
this
.
isFullScreen
=
false
,
});
}
class
BaseWebViewScreen
extends
BaseScreen
{
...
...
@@ -26,10 +32,13 @@ class BaseWebViewScreen extends BaseScreen {
State
<
BaseWebViewScreen
>
createState
()
=>
_BaseWebViewScreenState
();
}
class
_BaseWebViewScreenState
extends
BaseState
<
BaseWebViewScreen
>
with
BasicState
{
class
_BaseWebViewScreenState
extends
BaseState
<
BaseWebViewScreen
>
with
BasicState
{
late
final
BaseWebViewInput
input
;
WebViewController
?
_controller
;
// Nullable cho web platform
String
?
_dynamicTitle
;
Map
<
String
,
String
>?
_authHeaders
;
bool
_isReissuingNavigation
=
false
;
@override
void
initState
()
{
...
...
@@ -43,7 +52,6 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
});
return
;
}
// Web platform: mở URL trong tab mới và đóng màn hình ngay
if
(
kIsWeb
)
{
AppLoading
().
hide
();
...
...
@@ -60,7 +68,8 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
// Mobile platform: khởi tạo WebView
AppLoading
().
show
();
_controller
=
WebViewController
()
_controller
=
WebViewController
()
..
setJavaScriptMode
(
JavaScriptMode
.
unrestricted
)
..
setBackgroundColor
(
Colors
.
transparent
)
..
setNavigationDelegate
(
...
...
@@ -79,19 +88,17 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
print
(
'WebView error:
${error.description}
'
);
}
// Có thể hiển thị lỗi nếu cần
//
showAlertError(content: error.description);
showAlertError
(
content:
error
.
description
);
}
},
onNavigationRequest:
_handleNavigation
,
),
)
..
loadRequest
(
Uri
.
parse
(
formatUrl
(
input
.
url
)));
_clearCookies
();
);
_prepareInitialLoad
();
}
String
formatUrl
(
String
inputUrl
)
{
if
(
inputUrl
.
isEmpty
)
return
''
;
try
{
if
(!
inputUrl
.
startsWith
(
'http://'
)
&&
!
inputUrl
.
startsWith
(
'https://'
))
{
return
'https://
$inputUrl
'
;
...
...
@@ -111,16 +118,15 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
if
(
kIsWeb
)
{
return
const
SizedBox
.
shrink
();
}
// Mobile platform: sử dụng WebView
return
Scaffold
(
appBar:
input
.
isFullScreen
appBar:
input
.
isFullScreen
?
null
:
CustomNavigationBar
(
title:
input
.
title
??
_dynamicTitle
??
Uri
.
parse
(
input
.
url
).
host
,
leftButtons:
[
CustomBackButton
(
onPressed:
_handleBack
),
],
title:
input
.
title
??
_dynamicTitle
??
Uri
.
parse
(
input
.
url
).
host
,
leftButtons:
[
CustomBackButton
(
onPressed:
_handleBack
)],
),
body:
Stack
(
children:
[
...
...
@@ -128,12 +134,18 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
child:
WebViewWidget
(
controller:
_controller
!,
gestureRecognizers:
const
<
Factory
<
OneSequenceGestureRecognizer
>>{
Factory
<
VerticalDragGestureRecognizer
>(
VerticalDragGestureRecognizer
.
new
),
Factory
<
VerticalDragGestureRecognizer
>(
VerticalDragGestureRecognizer
.
new
,
),
},
),
),
if
(
input
.
isFullScreen
)
Positioned
(
top:
MediaQuery
.
of
(
context
).
padding
.
top
+
8
,
left:
8
,
child:
CustomBackButton
()),
Positioned
(
top:
MediaQuery
.
of
(
context
).
padding
.
top
+
8
,
left:
8
,
child:
CustomBackButton
(),
),
],
),
);
...
...
@@ -192,89 +204,106 @@ class _BaseWebViewScreenState extends BaseState<BaseWebViewScreen> with BasicSta
launchUrl
(
uri
);
return
NavigationDecision
.
prevent
;
}
if
(
_isReissuingNavigation
)
{
_isReissuingNavigation
=
false
;
return
NavigationDecision
.
navigate
;
}
// Widget _buildWebViewForWeb() {
// if (!kIsWeb) return const SizedBox.shrink();
//
// return Container(
// width: double.infinity,
// height: double.infinity,
// child: Column(
// children: [
// // Header với URL
// Container(
// padding: const EdgeInsets.all(16),
// color: Colors.grey[100],
// child: Row(
// children: [
// Icon(Icons.language, color: Colors.blue),
// const SizedBox(width: 8),
// Expanded(
// child: Text(
// input.url,
// style: const TextStyle(fontSize: 14),
// overflow: TextOverflow.ellipsis,
// ),
// ),
// const SizedBox(width: 8),
// ElevatedButton.icon(
// onPressed: () => _openUrlInBrowser(),
// icon: const Icon(Icons.open_in_new, size: 16),
// label: const Text('Mở tab mới'),
// style: ElevatedButton.styleFrom(
// padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// ),
// ),
// ],
// ),
// ),
// // iframe area
// Expanded(
// child: Container(
// width: double.infinity,
// height: double.infinity,
// decoration: BoxDecoration(
// border: Border.all(color: Colors.grey[300]!),
// ),
// child: Center(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Icon(Icons.web, size: 64, color: Colors.grey[400]),
// const SizedBox(height: 16),
// Text(
// 'WebView không hỗ trợ trực tiếp trên web',
// style: Theme.of(context).textTheme.headlineSmall?.copyWith(
// color: Colors.grey[600],
// ),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 8),
// Text(
// 'Vui lòng click "Mở tab mới" để xem nội dung',
// style: Theme.of(context).textTheme.bodyMedium?.copyWith(
// color: Colors.grey[500],
// ),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 24),
// ElevatedButton.icon(
// onPressed: () => _openUrlInBrowser(),
// icon: const Icon(Icons.open_in_new),
// label: const Text('Mở trong tab mới'),
// style: ElevatedButton.styleFrom(
// padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// );
// }
if
(
_shouldAttachHeaders
(
url
))
{
try
{
final
target
=
Uri
.
parse
(
url
);
_loadWithHeaders
(
target
);
return
NavigationDecision
.
prevent
;
}
catch
(
e
)
{
if
(
kDebugMode
)
{
print
(
'Failed to reissue navigation with headers:
$e
'
);
}
}
}
return
NavigationDecision
.
navigate
;
}
Future
<
void
>
_prepareInitialLoad
()
async
{
await
_clearCookies
();
final
formattedUrl
=
formatUrl
(
input
.
url
);
if
(
formattedUrl
.
isEmpty
)
{
AppLoading
().
hide
();
return
;
}
var
uri
=
Uri
.
parse
(
formattedUrl
);
final
token
=
DataPreference
.
instance
.
token
?.
trim
();
if
(
token
!=
null
&&
token
.
isNotEmpty
&&
defaultTargetPlatform
==
TargetPlatform
.
iOS
)
{
final
query
=
Map
<
String
,
String
>.
from
(
uri
.
queryParameters
);
query
[
'access_token'
]
=
token
;
uri
=
uri
.
replace
(
queryParameters:
query
);
}
await
_syncAuthCookie
(
uri
);
final
headers
=
await
_buildInitialHeaders
();
_authHeaders
=
headers
.
isEmpty
?
null
:
headers
;
try
{
await
_loadWithHeaders
(
uri
);
}
catch
(
e
)
{
if
(
kDebugMode
)
{
print
(
'WebView load with headers failed:
$e
. Retrying without headers.'
,
);
}
await
_controller
?.
loadRequest
(
uri
);
}
}
Future
<
void
>
_syncAuthCookie
(
Uri
uri
)
async
{
if
(!
DataPreference
.
instance
.
logged
)
return
;
final
token
=
DataPreference
.
instance
.
token
?.
trim
();
if
(
token
==
null
||
token
.
isEmpty
)
return
;
final
cookieManager
=
WebViewCookieManager
();
await
cookieManager
.
setCookie
(
WebViewCookie
(
name:
'access_token'
,
value:
token
,
domain:
uri
.
host
,
path:
'/'
,
),
);
}
Future
<
Map
<
String
,
String
>>
_buildInitialHeaders
()
async
{
if
(!
DataPreference
.
instance
.
logged
)
return
{};
final
token
=
DataPreference
.
instance
.
token
?.
trim
();
if
(
token
==
null
||
token
.
isEmpty
)
return
{};
try
{
final
version
=
await
AppInfoHelper
.
version
;
return
{
'access_token'
:
token
,
'version'
:
version
,
'Cookie'
:
'access_token=
$token
'
};
}
catch
(
_
)
{
return
{
'access_token'
:
token
,
'Cookie'
:
'access_token=
$token
'
};
}
}
Future
<
void
>
_loadWithHeaders
(
Uri
uri
)
async
{
if
(
_authHeaders
==
null
||
_authHeaders
!.
isEmpty
)
{
await
_controller
?.
loadRequest
(
uri
);
return
;
}
_isReissuingNavigation
=
true
;
print
(
'➡️ Loading with headers:
${uri.toString()}
, headers:
$_authHeaders
'
);
await
_controller
?.
loadRequest
(
uri
,
headers:
_authHeaders
!);
}
bool
_shouldAttachHeaders
(
String
url
)
{
if
(
_authHeaders
==
null
||
_authHeaders
!.
isEmpty
)
return
false
;
if
(
url
.
isEmpty
||
url
==
'about:blank'
)
return
false
;
final
lower
=
url
.
toLowerCase
();
return
lower
.
startsWith
(
'http://'
)
||
lower
.
startsWith
(
'https://'
);
}
}
lib/services/logout_service.dart
View file @
e9cf8244
...
...
@@ -3,11 +3,13 @@ import 'package:flutter/foundation.dart';
import
'../networking/dio_http_service.dart'
;
import
'../networking/restful_api_client.dart'
;
import
'../networking/restful_api_client_all_request.dart'
;
import
'../preference/data_preference.dart'
;
class
LogoutService
{
LogoutService
.
_
();
static
final
RestfulAPIClient
_client
=
RestfulAPIClient
(
DioHttpService
().
dio
);
static
Future
<
void
>
logout
()
async
{
if
(!
DataPreference
.
instance
.
logged
)
return
;
try
{
await
_client
.
logout
();
}
catch
(
e
)
{
...
...
lib/services/token_refresh_service.dart
View file @
e9cf8244
import
'package:flutter/widgets.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'
;
import
'../configs/constants.dart'
;
import
'../model/auth/login_token_response_model.dart'
;
import
'../networking/restful_api_viewmodel.dart'
;
import
'../preference/data_preference.dart'
;
...
...
@@ -65,15 +66,16 @@ class TokenRefreshService extends RestfulApiViewModel {
/// Xử lý refresh thất bại - redirect về login
Future
<
void
>
_handleRefreshFailure
()
async
{
final
logged
=
DataPreference
.
instance
.
logged
;
await
DataPreference
.
instance
.
clearData
();
for
(
final
callback
in
_callbacks
)
{
callback
(
false
);
}
_callbacks
.
clear
();
// Redirect về login screen (ensure on UI thread)
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
AppNavigator
.
showAuthAlertAndGoLogin
(
'Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại.'
);
if
(!
logged
)
return
;
AppNavigator
.
showAuthAlertAndGoLogin
(
ErrorCodes
.
tokenInvalidMessage
);
});
}
...
...
pubspec.yaml
View file @
e9cf8244
...
...
@@ -36,18 +36,16 @@ dependencies:
cupertino_icons
:
^1.0.8
shared_preferences
:
^2.5.2
json_annotation
:
^4.9.0
retrofit
:
get
:
^4.7.2
fluttertoast
:
^8.2.13
logger
:
logger
:
^2.4.0
url_launcher
:
^6.3.1
flutter_widget_from_html
:
^0.17.1
device_info_plus
:
^12.1.0
uuid
:
^4.3.3
universal_html
:
^2.2.4
flutter_svg
:
local_auth
:
pin_code_fields
:
local_auth
:
^2.1.7
pin_code_fields
:
^8.0.1
intl
:
^0.20.2
webview_flutter
:
^4.2.2
webview_flutter_wkwebview
:
^3.9.4
...
...
@@ -65,7 +63,6 @@ dependencies:
fl_chart
:
^1.1.0
mobile_scanner
:
^7.0.1
encrypt
:
^5.0.1
connectivity_plus
:
flutter_launcher_icons
:
^0.14.4
firebase_core
:
^4.1.0
firebase_messaging
:
^16.0.1
...
...
@@ -78,7 +75,6 @@ dev_dependencies:
sdk
:
flutter
build_runner
:
^2.4.15
json_serializable
:
^6.9.4
retrofit_generator
:
equatable
:
^2.0.7
# The "flutter_lints" package below contains a set of recommended lints to
...
...
Prev
1
2
3
4
5
6
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