Files
eatvio_app/lib/features/diary/presentation/bloc/glossary_sync_cubit.dart
2026-04-24 16:54:04 +03:00

123 lines
4.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}