110 lines
3.1 KiB
Dart
110 lines
3.1 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../providers/providers.dart';
|
|
|
|
enum AppBootstrapStatus {
|
|
bootstrapping,
|
|
noHomes,
|
|
ready,
|
|
authFailed,
|
|
networkFailed,
|
|
invalidConfig,
|
|
failed,
|
|
}
|
|
|
|
class AppBootstrapState {
|
|
final AppBootstrapStatus status;
|
|
final String? message;
|
|
|
|
const AppBootstrapState._(this.status, {this.message});
|
|
|
|
const AppBootstrapState.bootstrapping()
|
|
: this._(AppBootstrapStatus.bootstrapping);
|
|
|
|
const AppBootstrapState.noHomes() : this._(AppBootstrapStatus.noHomes);
|
|
|
|
const AppBootstrapState.ready() : this._(AppBootstrapStatus.ready);
|
|
|
|
const AppBootstrapState.authFailed(String message)
|
|
: this._(AppBootstrapStatus.authFailed, message: message);
|
|
|
|
const AppBootstrapState.networkFailed(String message)
|
|
: this._(AppBootstrapStatus.networkFailed, message: message);
|
|
|
|
const AppBootstrapState.invalidConfig(String message)
|
|
: this._(AppBootstrapStatus.invalidConfig, message: message);
|
|
|
|
const AppBootstrapState.failed(String message)
|
|
: this._(AppBootstrapStatus.failed, message: message);
|
|
|
|
bool get isBootstrapping => status == AppBootstrapStatus.bootstrapping;
|
|
}
|
|
|
|
final appBootstrapProvider =
|
|
NotifierProvider<AppBootstrapNotifier, AppBootstrapState>(
|
|
AppBootstrapNotifier.new,
|
|
);
|
|
|
|
class AppBootstrapNotifier extends Notifier<AppBootstrapState> {
|
|
@override
|
|
AppBootstrapState build() => const AppBootstrapState.bootstrapping();
|
|
|
|
Future<void> bootstrap() async {
|
|
state = const AppBootstrapState.bootstrapping();
|
|
|
|
try {
|
|
await ref.read(homesProvider.notifier).load();
|
|
await ref.read(currentHomeProvider.notifier).load();
|
|
|
|
final home = ref.read(currentHomeProvider);
|
|
if (home == null) {
|
|
await syncGeofenceTask(ref.read(homesProvider), currentHome: null);
|
|
state = const AppBootstrapState.noHomes();
|
|
return;
|
|
}
|
|
|
|
await ref.read(authInfoProvider.notifier).load(failOnError: true);
|
|
await syncGeofenceTask(ref.read(homesProvider), currentHome: home);
|
|
|
|
state = const AppBootstrapState.ready();
|
|
} catch (e) {
|
|
state = _failureState(e);
|
|
}
|
|
}
|
|
|
|
AppBootstrapState _failureState(Object error) {
|
|
if (error is StateError || error is FormatException) {
|
|
return AppBootstrapState.invalidConfig(error.toString());
|
|
}
|
|
|
|
if (error is DioException) {
|
|
final statusCode = error.response?.statusCode;
|
|
if (statusCode == 401 || statusCode == 403) {
|
|
return AppBootstrapState.authFailed(
|
|
'API key не прошёл авторизацию ($statusCode)',
|
|
);
|
|
}
|
|
|
|
if (_isNetworkFailure(error)) {
|
|
return AppBootstrapState.networkFailed(
|
|
'Backend недоступен: ${error.message ?? error.type.name}',
|
|
);
|
|
}
|
|
}
|
|
|
|
return AppBootstrapState.failed(error.toString());
|
|
}
|
|
|
|
bool _isNetworkFailure(DioException error) {
|
|
return switch (error.type) {
|
|
DioExceptionType.connectionTimeout ||
|
|
DioExceptionType.sendTimeout ||
|
|
DioExceptionType.receiveTimeout ||
|
|
DioExceptionType.connectionError ||
|
|
DioExceptionType.unknown => true,
|
|
_ => false,
|
|
};
|
|
}
|
|
}
|