✨ 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:dio_smart_retry/dio_smart_retry.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -16,6 +17,32 @@ import 'package:island/talker.dart';
|
|||||||
|
|
||||||
import 'config.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 imagePickerProvider = Provider((ref) => ImagePicker());
|
||||||
|
|
||||||
final userAgentProvider = FutureProvider<String>((ref) async {
|
final userAgentProvider = FutureProvider<String>((ref) async {
|
||||||
@@ -80,10 +107,8 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
|
|
||||||
dio.interceptors.addAll([
|
dio.interceptors.addAll([
|
||||||
InterceptorsWrapper(
|
InterceptorsWrapper(
|
||||||
onRequest: (
|
onRequest:
|
||||||
RequestOptions options,
|
(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||||
RequestInterceptorHandler handler,
|
|
||||||
) async {
|
|
||||||
try {
|
try {
|
||||||
final token = await getToken(ref.watch(tokenProvider));
|
final token = await getToken(ref.watch(tokenProvider));
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
@@ -99,6 +124,42 @@ final apiClientProvider = Provider<Dio>((ref) {
|
|||||||
}
|
}
|
||||||
return handler.next(options);
|
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(
|
TalkerDioLogger(
|
||||||
talker: talker,
|
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/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/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';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class NetworkStatusSheet extends HookConsumerWidget {
|
class NetworkStatusSheet extends HookConsumerWidget {
|
||||||
final bool autoClose;
|
final bool autoClose;
|
||||||
@@ -15,13 +17,22 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
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 wsNotifier = ref.watch(websocketStateProvider.notifier);
|
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||||
|
|
||||||
|
final checks = [
|
||||||
|
wsState == WebSocketState.connected(),
|
||||||
|
apiState == NetworkStatus.online,
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (!autoClose) return;
|
if (!autoClose) return;
|
||||||
|
|
||||||
final checks = [wsState == WebSocketState.connected()];
|
final checks = [
|
||||||
|
wsState == WebSocketState.connected(),
|
||||||
|
apiState == NetworkStatus.online,
|
||||||
|
];
|
||||||
if (!checks.any((e) => !e)) {
|
if (!checks.any((e) => !e)) {
|
||||||
Future.delayed(Duration(seconds: 3), () {
|
Future.delayed(Duration(seconds: 3), () {
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
@@ -29,18 +40,63 @@ class NetworkStatusSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [wsState]);
|
}, [wsState, apiState]);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
heightFactor: 0.6,
|
heightFactor: 0.6,
|
||||||
titleText: wsState == WebSocketState.connected()
|
titleText: !checks.any((e) => !e)
|
||||||
? 'Connection Status'
|
? 'Connection Status'
|
||||||
: 'Connection Issue',
|
: 'Connection Issues',
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
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(
|
Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
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(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
|||||||
Reference in New Issue
Block a user