✨ Connection sheet got API status
This commit is contained in:
@@ -5,6 +5,7 @@ import 'dart:io';
|
||||
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -16,6 +17,32 @@ import 'package:island/talker.dart';
|
||||
|
||||
import 'config.dart';
|
||||
|
||||
part 'network.g.dart';
|
||||
|
||||
// Network status enum to track different states
|
||||
enum NetworkStatus { online, maintenance, offline }
|
||||
|
||||
// Provider for network status using Riverpod v3 annotation
|
||||
@riverpod
|
||||
class NetworkStatusNotifier extends _$NetworkStatusNotifier {
|
||||
@override
|
||||
NetworkStatus build() {
|
||||
return NetworkStatus.online;
|
||||
}
|
||||
|
||||
void setOnline() {
|
||||
state = NetworkStatus.online;
|
||||
}
|
||||
|
||||
void setMaintenance() {
|
||||
state = NetworkStatus.maintenance;
|
||||
}
|
||||
|
||||
void setOffline() {
|
||||
state = NetworkStatus.offline;
|
||||
}
|
||||
}
|
||||
|
||||
final imagePickerProvider = Provider((ref) => ImagePicker());
|
||||
|
||||
final userAgentProvider = FutureProvider<String>((ref) async {
|
||||
@@ -80,10 +107,8 @@ final apiClientProvider = Provider<Dio>((ref) {
|
||||
|
||||
dio.interceptors.addAll([
|
||||
InterceptorsWrapper(
|
||||
onRequest: (
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
onRequest:
|
||||
(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
try {
|
||||
final token = await getToken(ref.watch(tokenProvider));
|
||||
if (token != null) {
|
||||
@@ -99,6 +124,42 @@ final apiClientProvider = Provider<Dio>((ref) {
|
||||
}
|
||||
return handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
// Check for 503 status code (Service Unavailable/Maintenance)
|
||||
if (response.statusCode == 503) {
|
||||
final networkStatusNotifier = ref.read(
|
||||
networkStatusProvider.notifier,
|
||||
);
|
||||
networkStatusNotifier.setMaintenance();
|
||||
} else if (response.statusCode != null &&
|
||||
response.statusCode! >= 200 &&
|
||||
response.statusCode! < 300) {
|
||||
// Set online status for successful responses
|
||||
final networkStatusNotifier = ref.read(
|
||||
networkStatusProvider.notifier,
|
||||
);
|
||||
networkStatusNotifier.setOnline();
|
||||
}
|
||||
return handler.next(response);
|
||||
},
|
||||
onError: (error, handler) {
|
||||
// Handle network errors and set offline status
|
||||
if (error.type == DioExceptionType.connectionTimeout ||
|
||||
error.type == DioExceptionType.receiveTimeout ||
|
||||
error.type == DioExceptionType.sendTimeout ||
|
||||
error.type == DioExceptionType.connectionError) {
|
||||
final networkStatusNotifier = ref.read(
|
||||
networkStatusProvider.notifier,
|
||||
);
|
||||
networkStatusNotifier.setOffline();
|
||||
} else if (error.response?.statusCode == 503) {
|
||||
final networkStatusNotifier = ref.read(
|
||||
networkStatusProvider.notifier,
|
||||
);
|
||||
networkStatusNotifier.setMaintenance();
|
||||
}
|
||||
return handler.next(error);
|
||||
},
|
||||
),
|
||||
TalkerDioLogger(
|
||||
talker: talker,
|
||||
|
||||
64
lib/pods/network.g.dart
Normal file
64
lib/pods/network.g.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'network.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(NetworkStatusNotifier)
|
||||
const networkStatusProvider = NetworkStatusNotifierProvider._();
|
||||
|
||||
final class NetworkStatusNotifierProvider
|
||||
extends $NotifierProvider<NetworkStatusNotifier, NetworkStatus> {
|
||||
const NetworkStatusNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'networkStatusProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$networkStatusNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
NetworkStatusNotifier create() => NetworkStatusNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(NetworkStatus value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<NetworkStatus>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$networkStatusNotifierHash() =>
|
||||
r'ca968c342be79cb97349fb95eee5c575d7076a99';
|
||||
|
||||
abstract class _$NetworkStatusNotifier extends $Notifier<NetworkStatus> {
|
||||
NetworkStatus build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<NetworkStatus, NetworkStatus>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<NetworkStatus, NetworkStatus>,
|
||||
NetworkStatus,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class NetworkStatusSheet extends HookConsumerWidget {
|
||||
final bool autoClose;
|
||||
@@ -15,13 +17,22 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ws = ref.watch(websocketProvider);
|
||||
final wsState = ref.watch(websocketStateProvider);
|
||||
final apiState = ref.watch(networkStatusProvider);
|
||||
|
||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||
|
||||
final checks = [
|
||||
wsState == WebSocketState.connected(),
|
||||
apiState == NetworkStatus.online,
|
||||
];
|
||||
|
||||
useEffect(() {
|
||||
if (!autoClose) return;
|
||||
|
||||
final checks = [wsState == WebSocketState.connected()];
|
||||
final checks = [
|
||||
wsState == WebSocketState.connected(),
|
||||
apiState == NetworkStatus.online,
|
||||
];
|
||||
if (!checks.any((e) => !e)) {
|
||||
Future.delayed(Duration(seconds: 3), () {
|
||||
if (context.mounted) Navigator.of(context).pop();
|
||||
@@ -29,18 +40,63 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [wsState]);
|
||||
}, [wsState, apiState]);
|
||||
|
||||
return SheetScaffold(
|
||||
heightFactor: 0.6,
|
||||
titleText: wsState == WebSocketState.connected()
|
||||
titleText: !checks.any((e) => !e)
|
||||
? 'Connection Status'
|
||||
: 'Connection Issue',
|
||||
: 'Connection Issues',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: !checks.any((e) => !e)
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.red.withOpacity(0.1),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('overview').tr().bold(),
|
||||
Column(
|
||||
spacing: 8,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!checks.any((e) => !e))
|
||||
Text('Everything is operational.'),
|
||||
if (!checks[0])
|
||||
Text(
|
||||
'WebSocket is disconnected. Realtime updates are not available. You can try tap the reconnect button below to try connect again.',
|
||||
),
|
||||
if (!checks[1])
|
||||
...([
|
||||
Text(
|
||||
'API is unreachable, you can try again later. If the issue persists, please contact support. Or you can check the service status.',
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString("https://status.solsynth.dev");
|
||||
},
|
||||
child: Text(
|
||||
'Check Service Status',
|
||||
).textColor(Colors.blueAccent).bold(),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
@@ -100,6 +156,42 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text('API').bold(),
|
||||
Text(
|
||||
apiState == NetworkStatus.online
|
||||
? 'Online'
|
||||
: apiState == NetworkStatus.maintenance
|
||||
? 'Under Maintenance'
|
||||
: 'Offline',
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: apiState == NetworkStatus.online
|
||||
? Icon(
|
||||
Symbols.check_circle,
|
||||
key: ValueKey(NetworkStatus.online),
|
||||
color: Colors.green,
|
||||
size: 16,
|
||||
)
|
||||
: apiState == NetworkStatus.maintenance
|
||||
? Icon(
|
||||
Symbols.construction,
|
||||
key: ValueKey(NetworkStatus.maintenance),
|
||||
color: Colors.orange,
|
||||
size: 16,
|
||||
)
|
||||
: Icon(
|
||||
Symbols.cloud_off,
|
||||
key: ValueKey(NetworkStatus.offline),
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
spacing: 8,
|
||||
|
||||
Reference in New Issue
Block a user