forked from eatvio/eatvio_app
123 lines
4.3 KiB
Dart
123 lines
4.3 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
|
||
import 'package:eatvio_app/core/local_db/services/glossary_sync_runner.dart';
|
||
import 'package:eatvio_app/features/diary/presentation/bloc/glossary_sync_state.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
|
||
/// Кубит для отслеживания синхронизации глоссария продуктов.
|
||
///
|
||
/// Подписывается на [InternetConnectionCubit] — при появлении сети
|
||
/// автоматически запускает синхронизацию. Эмитит детальные стадии:
|
||
/// checking → downloading → processing → completed/error.
|
||
///
|
||
/// Используется в [GlossarySyncBannerWidget] для показа прогресса в UI.
|
||
class GlossarySyncCubit extends Cubit<GlossarySyncState> {
|
||
final GlossarySyncService _syncService;
|
||
final InternetConnectionCubit _connectionCubit;
|
||
|
||
StreamSubscription? _connectionSub;
|
||
bool _isSyncing = false;
|
||
|
||
/// Таймер автоскрытия баннера после успешной синхронизации
|
||
Timer? _hideTimer;
|
||
|
||
GlossarySyncCubit({
|
||
required GlossarySyncService syncService,
|
||
required InternetConnectionCubit connectionCubit,
|
||
}) : _syncService = syncService,
|
||
_connectionCubit = connectionCubit,
|
||
super(const GlossarySyncState.idle());
|
||
|
||
/// Начать прослушивание сети и автозапуск синхронизации.
|
||
void start() {
|
||
_connectionSub = _connectionCubit.stream.listen((state) {
|
||
if (state.isInternetAvailable && !_isSyncing) {
|
||
syncNow();
|
||
}
|
||
});
|
||
|
||
// Запустить немедленно если уже online
|
||
if (_connectionCubit.state.isInternetAvailable) {
|
||
syncNow();
|
||
}
|
||
}
|
||
|
||
/// Принудительно запустить синхронизацию (можно вызвать из UI).
|
||
Future<void> syncNow() async {
|
||
if (_isSyncing) return;
|
||
_isSyncing = true;
|
||
_hideTimer?.cancel();
|
||
|
||
try {
|
||
final count = await _syncService.sync(
|
||
onStageChanged: _onStageChanged,
|
||
onDownloadProgress: _onDownloadProgress,
|
||
onProcessingProgress: _onProcessingProgress,
|
||
);
|
||
|
||
if (count == 0) {
|
||
emit(const GlossarySyncState.upToDate());
|
||
} else {
|
||
emit(GlossarySyncState.completed(syncedCount: count));
|
||
}
|
||
|
||
// Автоскрытие через 4 секунды
|
||
_hideTimer = Timer(const Duration(seconds: 4), () {
|
||
if (!isClosed) emit(const GlossarySyncState.idle());
|
||
});
|
||
} catch (e) {
|
||
emit(GlossarySyncState.error(message: _humanReadableError(e)));
|
||
// Скрыть ошибку через 6 секунд
|
||
_hideTimer = Timer(const Duration(seconds: 6), () {
|
||
if (!isClosed) emit(const GlossarySyncState.idle());
|
||
});
|
||
} finally {
|
||
_isSyncing = false;
|
||
}
|
||
}
|
||
|
||
void _onStageChanged(GlossarySyncStage stage) {
|
||
if (isClosed) return;
|
||
switch (stage) {
|
||
case GlossarySyncStage.checking:
|
||
emit(const GlossarySyncState.checking());
|
||
case GlossarySyncStage.downloading:
|
||
emit(const GlossarySyncState.downloading());
|
||
case GlossarySyncStage.processing:
|
||
emit(const GlossarySyncState.processing(processed: 0, total: 0));
|
||
case GlossarySyncStage.saving:
|
||
// Остаёмся на processing — saving идёт мгновенно
|
||
break;
|
||
}
|
||
}
|
||
|
||
void _onDownloadProgress(double progress) {
|
||
if (isClosed) return;
|
||
emit(GlossarySyncState.downloading(progress: progress));
|
||
}
|
||
|
||
void _onProcessingProgress(int processed, int total) {
|
||
if (isClosed) return;
|
||
emit(GlossarySyncState.processing(processed: processed, total: total));
|
||
}
|
||
|
||
String _humanReadableError(Object e) {
|
||
final msg = e.toString();
|
||
if (msg.contains('SocketException') || msg.contains('connection')) {
|
||
return 'Нет соединения с сервером';
|
||
}
|
||
if (msg.contains('TimeoutException') || msg.contains('timeout')) {
|
||
return 'Сервер не отвечает';
|
||
}
|
||
return 'Ошибка синхронизации глоссария';
|
||
}
|
||
|
||
@override
|
||
Future<void> close() {
|
||
_connectionSub?.cancel();
|
||
_hideTimer?.cancel();
|
||
return super.close();
|
||
}
|
||
}
|