✨ API network request status
This commit is contained in:
@@ -20,7 +20,7 @@ import 'config.dart';
|
|||||||
part 'network.g.dart';
|
part 'network.g.dart';
|
||||||
|
|
||||||
// Network status enum to track different states
|
// Network status enum to track different states
|
||||||
enum NetworkStatus { online, maintenance, offline }
|
enum NetworkStatus { online, notReady, maintenance, offline }
|
||||||
|
|
||||||
// Provider for network status using Riverpod v3 annotation
|
// Provider for network status using Riverpod v3 annotation
|
||||||
@riverpod
|
@riverpod
|
||||||
@@ -41,6 +41,10 @@ class NetworkStatusNotifier extends _$NetworkStatusNotifier {
|
|||||||
void setOffline() {
|
void setOffline() {
|
||||||
state = NetworkStatus.offline;
|
state = NetworkStatus.offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setNotReady() {
|
||||||
|
state = NetworkStatus.notReady;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final imagePickerProvider = Provider((ref) => ImagePicker());
|
final imagePickerProvider = Provider((ref) => ImagePicker());
|
||||||
@@ -130,7 +134,11 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
final networkStatusNotifier = ref.read(
|
final networkStatusNotifier = ref.read(
|
||||||
networkStatusProvider.notifier,
|
networkStatusProvider.notifier,
|
||||||
);
|
);
|
||||||
networkStatusNotifier.setMaintenance();
|
if (response.headers.value('X-NotReady') != null) {
|
||||||
|
networkStatusNotifier.setNotReady();
|
||||||
|
} else {
|
||||||
|
networkStatusNotifier.setMaintenance();
|
||||||
|
}
|
||||||
} else if (response.statusCode != null &&
|
} else if (response.statusCode != null &&
|
||||||
response.statusCode! >= 200 &&
|
response.statusCode! >= 200 &&
|
||||||
response.statusCode! < 300) {
|
response.statusCode! < 300) {
|
||||||
@@ -156,7 +164,11 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
final networkStatusNotifier = ref.read(
|
final networkStatusNotifier = ref.read(
|
||||||
networkStatusProvider.notifier,
|
networkStatusProvider.notifier,
|
||||||
);
|
);
|
||||||
networkStatusNotifier.setMaintenance();
|
if (error.response?.headers.value('X-NotReady') != null) {
|
||||||
|
networkStatusNotifier.setNotReady();
|
||||||
|
} else {
|
||||||
|
networkStatusNotifier.setMaintenance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return handler.next(error);
|
return handler.next(error);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,71 +36,41 @@ class UserInfoNotifier extends AsyncNotifier<SnAccount?> {
|
|||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
if (!kIsWeb) {
|
if (error is DioException) {
|
||||||
if (error is DioException) {
|
if (error.response?.statusCode == 503) return null;
|
||||||
showOverlayDialog<bool>(
|
showOverlayDialog<bool>(
|
||||||
builder:
|
builder: (context, close) => AlertDialog(
|
||||||
(context, close) => AlertDialog(
|
title: Text('failedToLoadUserInfo'.tr()),
|
||||||
title: Text('failedToLoadUserInfo'.tr()),
|
content: Text(
|
||||||
content: Text(
|
[
|
||||||
[
|
(error.response?.statusCode == 401
|
||||||
(error.response?.statusCode == 401
|
? 'failedToLoadUserInfoUnauthorized'
|
||||||
? 'failedToLoadUserInfoUnauthorized'
|
: 'failedToLoadUserInfoNetwork')
|
||||||
: 'failedToLoadUserInfoNetwork')
|
.tr()
|
||||||
.tr()
|
.trim(),
|
||||||
.trim(),
|
'',
|
||||||
'',
|
'${error.response?.statusCode ?? 'Network Error'}',
|
||||||
'${error.response?.statusCode ?? 'Network Error'}',
|
if (error.response?.headers != null) error.response?.headers,
|
||||||
if (error.response?.headers != null)
|
if (error.response?.data != null)
|
||||||
error.response?.headers,
|
jsonEncode(error.response?.data),
|
||||||
if (error.response?.data != null)
|
].join('\n'),
|
||||||
jsonEncode(error.response?.data),
|
),
|
||||||
].join('\n'),
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
actions: [
|
onPressed: () => close(false),
|
||||||
TextButton(
|
child: Text('okay'.tr()),
|
||||||
onPressed: () => close(false),
|
),
|
||||||
child: Text('okay'.tr()),
|
TextButton(
|
||||||
),
|
onPressed: () => close(true),
|
||||||
TextButton(
|
child: Text('retry'.tr()),
|
||||||
onPressed: () => close(true),
|
),
|
||||||
child: Text('retry'.tr()),
|
],
|
||||||
),
|
),
|
||||||
],
|
).then((value) {
|
||||||
),
|
if (value == true) {
|
||||||
).then((value) {
|
ref.invalidateSelf();
|
||||||
if (value == true) {
|
}
|
||||||
ref.invalidateSelf();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showOverlayDialog<bool>(
|
|
||||||
builder:
|
|
||||||
(context, close) => AlertDialog(
|
|
||||||
title: Text('failedToLoadUserInfo'.tr()),
|
|
||||||
content: Text(
|
|
||||||
[
|
|
||||||
'failedToLoadUserInfoNetwork'.tr(),
|
|
||||||
error.toString(),
|
|
||||||
].join('\n\n').trim(),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => close(false),
|
|
||||||
child: Text('okay'.tr()),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => close(true),
|
|
||||||
child: Text('retry'.tr()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
ref.invalidateSelf();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
talker.error(
|
talker.error(
|
||||||
"[UserInfo] Failed to fetch user info...",
|
"[UserInfo] Failed to fetch user info...",
|
||||||
|
|||||||
@@ -34,22 +34,43 @@ class AppWrapper extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final networkStateShowing = useState(false);
|
final networkStateShowing = useState(false);
|
||||||
final websocketState = ref.watch(websocketStateProvider);
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
|
final apiState = ref.watch(networkStatusProvider);
|
||||||
final isShowSnow = useState(false);
|
final isShowSnow = useState(false);
|
||||||
final isSnowGone = useState(false);
|
final isSnowGone = useState(false);
|
||||||
|
|
||||||
// Handle network status modal
|
// Handle network status modal
|
||||||
if (websocketState == WebSocketState.duplicateDevice() &&
|
useEffect(() {
|
||||||
!networkStateShowing.value) {
|
bool triedOpen = false;
|
||||||
networkStateShowing.value = true;
|
if (websocketState == WebSocketState.duplicateDevice() &&
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
!networkStateShowing.value &&
|
||||||
showModalBottomSheet(
|
!triedOpen) {
|
||||||
context: context,
|
networkStateShowing.value = true;
|
||||||
isScrollControlled: true,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
isDismissible: false,
|
showModalBottomSheet(
|
||||||
builder: (context) => NetworkStatusSheet(autoClose: true),
|
context: context,
|
||||||
).then((_) => networkStateShowing.value = false);
|
isScrollControlled: true,
|
||||||
});
|
isDismissible: false,
|
||||||
}
|
builder: (context) => NetworkStatusSheet(autoClose: true),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
triedOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiState != NetworkStatus.online &&
|
||||||
|
!networkStateShowing.value &&
|
||||||
|
!triedOpen) {
|
||||||
|
networkStateShowing.value = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const NetworkStatusSheet(),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
triedOpen = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [websocketState, apiState]);
|
||||||
|
|
||||||
// Initialize services and listeners
|
// Initialize services and listeners
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -18,6 +19,7 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
final ws = ref.watch(websocketProvider);
|
final ws = ref.watch(websocketProvider);
|
||||||
final wsState = ref.watch(websocketStateProvider);
|
final wsState = ref.watch(websocketStateProvider);
|
||||||
final apiState = ref.watch(networkStatusProvider);
|
final apiState = ref.watch(networkStatusProvider);
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
|
||||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||||
|
|
||||||
@@ -163,6 +165,8 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
apiState == NetworkStatus.online
|
apiState == NetworkStatus.online
|
||||||
? 'Online'
|
? 'Online'
|
||||||
|
: apiState == NetworkStatus.notReady
|
||||||
|
? 'Not Ready'
|
||||||
: apiState == NetworkStatus.maintenance
|
: apiState == NetworkStatus.maintenance
|
||||||
? 'Under Maintenance'
|
? 'Under Maintenance'
|
||||||
: 'Offline',
|
: 'Offline',
|
||||||
@@ -176,6 +180,13 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
size: 16,
|
size: 16,
|
||||||
)
|
)
|
||||||
|
: apiState == NetworkStatus.notReady
|
||||||
|
? Icon(
|
||||||
|
Symbols.warning,
|
||||||
|
key: ValueKey(NetworkStatus.notReady),
|
||||||
|
color: Colors.orange,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
: apiState == NetworkStatus.maintenance
|
: apiState == NetworkStatus.maintenance
|
||||||
? Icon(
|
? Icon(
|
||||||
Symbols.construction,
|
Symbols.construction,
|
||||||
@@ -192,6 +203,13 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Text('API Server').bold(),
|
||||||
|
Expanded(child: Text(serverUrl)),
|
||||||
|
],
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
|||||||
Reference in New Issue
Block a user