fix(diary): some fixes offline-diary

This commit is contained in:
mso7nueh
2026-05-14 19:52:15 +03:00
parent 63fc824449
commit af3b40d5f2
11 changed files with 114 additions and 228 deletions

View File

@@ -156,7 +156,14 @@ class DiaryCubit extends Cubit<DiaryState> {
}
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
log('updateCurrentDiary error: $e');
// BUG-1 fix: офлайн fallback — перезагружаем из кэша, чтобы UI
// отразил изменения, выполненные пользователем.
try {
await fetchDiaryV2(needLoading: false, diaryId: diaryId);
} catch (_) {
// Не критично — UI останется на текущем состоянии
}
}
}
@@ -351,8 +358,9 @@ class DiaryCubit extends Cubit<DiaryState> {
planDuration = chosenPlanModel.plan.duration;
planName = chosenPlanModel.plan.name;
currentPlanDay = consultantReview.currentPlanDay;
} catch (_) {
// Офлайн — данные плана недоступны
} catch (e) {
// BUG-9 fix: офлайн — данные плана недоступны, логируем для отладки
log('[setDiary] Plan/comments data unavailable (offline?): $e');
}
}
@@ -431,7 +439,11 @@ class DiaryCubit extends Cubit<DiaryState> {
required int quantity,
int? productNutritionalValueId,
}) async {
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
// BUG-2 fix: безопасная проверка типа стейта перед кастом
final currentState = state;
if (currentState is DiaryStateLoaded) {
emit(currentState.copyWith(isLoadingAddingProductInMeal: true));
}
try {
await diaryRepository.addPortionToMeal(
diaryId: diaryId,

View File

@@ -159,8 +159,8 @@ class _DiaryScreenContentState extends State<DiaryScreenContent> {
});
final isFinished = await context.read<DiaryCubit>().finishDiaryDay(
diaryId: state.diary.id,
wakeUp: state.diary.wakeup.toString(),
retire: state.diary.retire.toString(),
wakeUp: state.diary.wakeup ?? '06:30:00',
retire: state.diary.retire ?? '22:30:00',
);
if (!isFinished && context.mounted) {
showErrorDialog(context);

View File

@@ -170,10 +170,19 @@ class _AddProductModalState extends State<AddProductModal> {
if (widget.portion != null) const SizedBox(width: 8),
InkWell(
onTap: () {
// BUG-4 fix: валидация полей перед отправкой
final productName = productNameController.text.trim();
if (productName.isEmpty) return;
final isGrams = selectedGlossary == null || selectedGlossary!.grInPortion == 0;
final portionType = isGrams ? 'gr' : 'pcs';
// BUG-4 fix: безопасный парсинг количества с дефолтом
final quantity = int.tryParse(quantityController.text) ?? (isGrams ? 100 : 1);
// Автоматически выбираем первый элемент, если текст точно совпадает
if (selectedGlossary == null &&
glossariesList.isNotEmpty &&
productNameController.text.trim().toLowerCase() == glossariesList[0].name.toLowerCase()) {
productName.toLowerCase() == glossariesList[0].name.toLowerCase()) {
selectedGlossary = glossariesList[0];
}
@@ -181,13 +190,9 @@ class _AddProductModalState extends State<AddProductModal> {
widget.diaryCubit.addProductToMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
productName: productName,
portion: portionType,
quantity: quantity,
productNutritionalValueId: selectedGlossary?.id,
);
} else {
@@ -195,26 +200,22 @@ class _AddProductModalState extends State<AddProductModal> {
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
portionId: widget.portion!.id,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
productName: productName,
portion: portionType,
quantity: quantity,
productNutritionalValueId: selectedGlossary?.id,
portionState: widget.portion!.portionState,
);
}
Navigator.of(context).pop();
if (selectedGlossary == null && _previousProductName != productNameController.text) {
if (selectedGlossary == null && _previousProductName != productName) {
showModalBottomSheet(
isScrollControlled: true,
useRootNavigator: true,
context: context,
builder: (context) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: GlossaryModerationRequestModalWidget(productName: productNameController.text, diaryCubit: widget.diaryCubit),
child: GlossaryModerationRequestModalWidget(productName: productName, diaryCubit: widget.diaryCubit),
),
);
}

View File

@@ -116,7 +116,13 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
builder: (context, state) {
if (state is DiaryStateLoaded) {
int mealIndex = state.selectedMeal - 1;
if (state.diary.meals[mealIndex].photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) {
final photos = state.diary.meals[mealIndex].photos;
// BUG-5 fix: сбросить индекс, если он вышел за пределы
// (например, после переключения на meal с меньшим числом фото)
if (currentSelectedPhotoIndex >= photos.length) {
currentSelectedPhotoIndex = 0;
}
if (photos.isEmpty && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented) {
return GestureDetector(
onTap: () async {
showImagePicker(state.diary);

View File

@@ -25,6 +25,7 @@ class ProductWidget extends StatefulWidget {
class _ProductWidgetState extends State<ProductWidget> {
late final DiaryCubit diaryCubit;
late String portionName;
// BUG-8 fix: инициализируем из актуального state порции
late bool isEaten;
bool showCommentWidget = false;
@override
@@ -32,7 +33,7 @@ class _ProductWidgetState extends State<ProductWidget> {
super.initState();
diaryCubit = context.read<DiaryCubit>();
portionName = widget.portion.portion == 'gr' ? 'гр' : 'шт';
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
isEaten = widget.portion.portionState == PortionState.eatenFromDiet;
}
Widget _buildNutritionalInfo() {
@@ -93,7 +94,8 @@ class _ProductWidgetState extends State<ProductWidget> {
void didUpdateWidget(ProductWidget oldWidget) {
super.didUpdateWidget(oldWidget);
portionName = widget.portion.portion == 'gr' ? 'гр' : 'шт';
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
// BUG-8 fix: синхронизируем isEaten с актуальным state порции
isEaten = widget.portion.portionState == PortionState.eatenFromDiet;
}
Future updateProductInMeal(DiaryStateLoaded state, PortionModel portion) {

View File

@@ -23,6 +23,12 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
diaryCubit = context.read<DiaryCubit>();
}
/// Безопасное форматирование времени (HH:MM:SS → HH:MM, пустое → '--:--')
String _formatTime(String time) {
if (time.isEmpty || time.length < 5) return '--:--';
return time.substring(0, 5);
}
void showTimePicker(int diaryId) {
showModalBottomSheet<void>(
context: context,
@@ -110,7 +116,7 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.meals[state.selectedMeal - 1].time.substring(0, state.diary.meals[state.selectedMeal - 1].time.length - 3),
_formatTime(state.diary.meals[state.selectedMeal - 1].time),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,

View File

@@ -26,6 +26,12 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
diaryCubit = context.read<DiaryCubit>();
}
/// BUG-3 fix: безопасное форматирование времени (HH:MM:SS → HH:MM, null → '--:--')
String _formatTime(String? time) {
if (time == null || time.length < 5) return '--:--';
return time.substring(0, 5);
}
void showTimePicker(int diaryId, bool isWakeup) {
showModalBottomSheet<void>(
context: context,
@@ -142,10 +148,7 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.wakeup.toString().substring(
0,
state.diary.wakeup.toString().length - 3,
),
_formatTime(state.diary.wakeup),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
@@ -201,10 +204,7 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
state.diary.retire.toString().substring(
0,
state.diary.retire.toString().length - 3,
),
_formatTime(state.diary.retire),
style: const TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,