Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ app.*.map.json

*.freezed.*
*.g.*

assets/.env/
2 changes: 1 addition & 1 deletion frontend/lib/core/presentation/widgets/app_toast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void showToast(bool isSuccess, String text) {
? Icon(Icons.check, color: Color(0xFF7FB069))
: Icon(Icons.close, color: Color(0xFFE76F51)),
SizedBox(width: 12.0),
Text(text, style: const TextStyle(color: Color(0xFFE5C9A8))),
Expanded(child: Text(text, style: const TextStyle(color: Color(0xFFE5C9A8)))),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:cookify/core/presentation/widgets/cookify_loading_content.dart';
import 'package:cookify/features/recipe/recipe_feed/presentation/bloc/recipe_feed_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CookifyPaginationListView<T extends Widget> extends StatelessWidget {
const CookifyPaginationListView({
Expand Down Expand Up @@ -33,13 +35,20 @@ class CookifyPaginationListView<T extends Widget> extends StatelessWidget {
onNotification: _onScrollNotification,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: ListView.separated(
controller: controller,
itemBuilder: (_, index) => index != items.length
? items[index]
: const CookifyLoadingContent(),
separatorBuilder: (_, _) => const SizedBox(height: 24.0),
itemCount: isLoading ? items.length + 1 : items.length,
child: RefreshIndicator(
onRefresh: () async {
await context.read<RecipeFeedCubit>().getRecipeList();
},
backgroundColor: Color(0xFF1A0F0A),
color: Color(0xFFE5C9A8),
child: ListView.separated(
controller: controller,
itemBuilder: (_, index) => index != items.length
? items[index]
: const CookifyLoadingContent(),
separatorBuilder: (_, _) => const SizedBox(height: 24.0),
itemCount: isLoading ? items.length + 1 : items.length,
),
),
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:math';
import 'dart:ui';

import 'package:flutter/widgets.dart';

Expand Down
14 changes: 9 additions & 5 deletions frontend/lib/di/di.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ abstract class Di {
}

static Future<void> initStorages(String address) async {
dio = Dio(BaseOptions(baseUrl: 'https://$address'));
dio = Dio(
BaseOptions(
baseUrl: 'https://$address',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
),
);
Di.dio = dio;
dio.interceptors.add(PrettyDioLogger());
dio.interceptors.add(FailureInterceptor());
Expand Down Expand Up @@ -83,15 +89,13 @@ abstract class Di {
class FailureInterceptor extends InterceptorsWrapper {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.type == DioExceptionType.connectionError ||
if (err.type == DioExceptionType.connectionError ||
err.type == DioExceptionType.connectionTimeout) {

// Создаем кастомную ошибку, сохраняя контекст запроса
throw NetworkException();
}

// Если это не ошибка сети, пропускаем ошибку дальше
return handler.next(err);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class AuthBar extends StatelessWidget {
isSelected: type == AuthPageContentType.signUp,
title: MyLocale.of(context).authBarSignUp,
),
AuthBarItem(
onTap: () {
onTypeChanged(AuthPageContentType.restore);
},
isSelected: type == AuthPageContentType.restore,
title: MyLocale.of(context).authBarRestore,
),
// AuthBarItem(
// onTap: () {
// onTypeChanged(AuthPageContentType.restore);
// },
// isSelected: type == AuthPageContentType.restore,
// title: MyLocale.of(context).authBarRestore,
// ),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class AuthTextField extends StatefulWidget {
const AuthTextField({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import 'dart:convert';

import 'package:cookify/core/data/mappers/failure_mapper.dart';
import 'package:cookify/core/domain/my_either/my_either.dart';
import 'package:cookify/di/di.dart';
import 'package:cookify/features/profile/data/local/user_statistic_local_store.dart';
import 'package:cookify/features/profile/data/data_sources/profile_remote_data_source.dart';
import 'package:cookify/features/profile/data/mappers/update_avatar_mapper.dart';
import 'package:cookify/features/profile/data/mappers/user_mapper.dart';
import 'package:cookify/features/profile/data/models/user_model.dart';
import 'package:cookify/features/profile/domain/entities/user_entity.dart';
import 'package:cookify/features/profile/domain/payloads/update_avatar_payload.dart';
import 'package:cookify/features/profile/domain/repositories/profile_repository.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:fpdart/fpdart.dart';

final class ProfileRepositoryImpl implements ProfileRepository {
Expand All @@ -22,7 +27,25 @@ final class ProfileRepositoryImpl implements ProfileRepository {
@override
Future<MyEither<UserEntity>> getUser() async {
try {
final userModel = await _remoteDataSource.getUser();
UserModel? userModel;
try {
userModel = await _remoteDataSource.getUser();
Di.getIt<FlutterSecureStorage>().write(
key: 'profile',
value: jsonEncode(userModel.toJson()),
);
} catch (e) {
try {
userModel = UserModel.fromJson(
jsonDecode(
await Di.getIt<FlutterSecureStorage>().read(key: 'profile')
as String,
),
);
} catch (_) {
throw e;
}
}
final userEntity = UserMapper.toEntity(userModel);
final statisticDelta = await _userStatisticLocalStore.getDelta();
final actualUserEntity = userEntity.copyWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class ProfileSettings extends StatelessWidget {

ProfileSettingsLocale(locale: locale),

const SizedBox(height: 12.0),
// const SizedBox(height: 12.0),

const _ChangePasswordButton(),
// const _ChangePasswordButton(),

const SizedBox(height: 12.0),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'package:cookify/features/profile/presentation/bloc/profile_event.dart';
import 'package:cookify/features/recipe/recipe_search/presentation/pages/recipe_search_form_page_content.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';

class ProfileUserInfo extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class RecipeCommonSearchRemoteDataSourceImpl
SearchCategoryListRequest request,
) async {
final response = await _dio.get(
'/api/search/tags?${request.lastId != null ? 'lastId=${request.lastId}' : ''}&name=${request.name}',
'/api/search/tags?name=${request.name}',
);

return (response.data as List)
Expand All @@ -29,7 +29,7 @@ class RecipeCommonSearchRemoteDataSourceImpl
SearchIngredientListRequest request,
) async {
final response = await _dio.get(
'/api/search/ingredients?${request.lastId != null ? 'lastId=${request.lastId}' : ''}&name=${request.name}',
'/api/search/ingredients?name=${request.name}',
);

return (response.data as List)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class _CategoryTextFieldState extends State<CategoryTextField> {
),
dense: true,
onTap: () {
_textController.text = category.name;
widget.controller.controller.text = category.name;
widget.controller.selectCategory(category);
_focusNode.unfocus();
},
Expand All @@ -174,7 +174,7 @@ class _CategoryTextFieldState extends State<CategoryTextField> {
children: [
Expanded(
child: CookifyTextField(
controller: _textController,
controller: widget.controller.controller,
focusNode: _focusNode,
onChanged: widget.onChanged,
hint: MyLocale.of(context).searchCategoryHint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class _IngredientTextFieldState extends State<IngredientTextField> {
),
dense: true,
onTap: () {
_textController.text = category.name;
widget.controller.controller.text = category.name;
widget.controller.selectIngredient(category);
_focusNode.unfocus();
},
Expand All @@ -176,7 +176,7 @@ class _IngredientTextFieldState extends State<IngredientTextField> {
children: [
Expanded(
child: CookifyTextField(
controller: _textController,
controller: widget.controller.controller,
focusNode: _focusNode,
onChanged: widget.onChanged,
hint: MyLocale.of(context).searchIngredientHint,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,86 @@
import 'dart:convert';
import 'dart:io';

import 'package:cookify/features/recipe/recipe_form/data/data_sources/recipe_form_remote_data_source.dart';
import 'package:cookify/features/recipe/recipe_form/domain/payloads/publish_recipe_payload.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart' as path;

class RecipeFormRemoteDataSourceImpl implements RecipeFormRemoteDataSource {
RecipeFormRemoteDataSourceImpl({required Dio dio}) : _dio = dio;

final Dio _dio;

Future<String> _fileToBase64(File file) async {
List<int> bytes = await file.readAsBytes();
String base64String = base64Encode(bytes);
String mimeType = _getMimeType(file.path);
return 'data:$mimeType;base64,$base64String';
}

// Функция для определения MIME типа
String _getMimeType(String filePath) {
String extension = path.extension(filePath).toLowerCase();
switch (extension) {
case '.jpg':
case '.jpeg':
return 'image/jpeg';
case '.png':
return 'image/png';
case '.gif':
return 'image/gif';
case '.webp':
return 'image/webp';
default:
return 'image/jpeg';
}
}

@override
Future<void> publishRecipe(PublishRecipePayload payload) async {
List<Map<String, dynamic>> imagesData = [];
for (int i = 0; i < payload.photos.length; i++) {
String base64Image = await _fileToBase64(File(payload.photos[i].path));
imagesData.add({'url': base64Image, 'order': i});
}

// Конвертируем фото шагов в base64
List<Map<String, dynamic>> stepsData = [];
for (int i = 0; i < payload.steps.length; i++) {
Map<String, dynamic> stepData = {
'title': payload.steps[i].title,
'description': payload.steps[i].description,
};

if (payload.steps[i].photoPath != null) {
String base64Image = await _fileToBase64(
File(payload.steps[i].photoPath!),
);
stepData['image_base64'] = base64Image;
} else {
stepData['image_base64'] = null;
}

stepsData.add(stepData);
}

await _dio.post(
'/api/recipes',
data: {
'name': payload.name,
'description': payload.description,
'cpfc': {
'calories': payload.calories,
'proteins': payload.proteins,
'fats': payload.fats,
'carbohydrates': payload.carbohydrates,
},
'difficulty': payload.difficulty,
'cookingTimeMinutes': payload.cookingTimeMinutes,
'categories': payload.categories,
'photos': payload.photos.map((file) => file.path).toList(),
'ingredients': payload.ingredients
.map(
(item) => {
'id': item.id,
'amount': item.amount,
'unit': item.unit,
},
)
.toList(),
'steps': payload.steps
.map(
(item) => {
'title': item.title,
'description': item.description,
if (item.photoPath != null) 'photo': item.photoPath,
},
)
"title": payload.name,
"cooking_time_minutes": payload.cookingTimeMinutes,
"servings": 1,
"calories100g": payload.calories,
"protein100g": payload.proteins,
"fat100g": payload.fats,
"carb100g": payload.carbohydrates,
"description": payload.description,
"difficulty": payload.difficulty.index,
"main_image_base64": imagesData.first['url'],
"steps": stepsData,
"tags": payload.categories,
"ingredients": payload.ingredients
.map((i) => {'id': i.id, 'amount': i.amount, 'unit': i.unit})
.toList(),
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:cookify/features/recipe/recipe_common/domain/enums/recipe_difficulty.dart';
import 'package:image_picker/image_picker.dart';

class PublishRecipePayload {
Expand All @@ -22,9 +23,9 @@ class PublishRecipePayload {
final int proteins;
final int fats;
final int carbohydrates;
final String difficulty;
final RecipeDifficulty difficulty;
final int cookingTimeMinutes;
final List<String> categories;
final List<int> categories;
final List<PublishRecipeIngredientPayload> ingredients;
final List<PublishRecipeStepPayload> steps;
final List<XFile> photos;
Expand Down
Loading
Loading