forked from eatvio/eatvio_app
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -16,7 +16,7 @@ jobs:
|
||||
run: |
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" /Users/gitea_runner_ci/Library/Keychains/login.keychain-db
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: EeChahboo9Ei
|
||||
KEYCHAIN_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
|
||||
|
||||
- name: Flutter build
|
||||
run: |
|
||||
@@ -30,8 +30,7 @@ jobs:
|
||||
env:
|
||||
LANG: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
MATCH_PASSWORD: EeChahboo9Ei
|
||||
MATCH_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
|
||||
MATCH_GIT_URL: http://gitea.office.local:3000/mso7nueh/fastlane
|
||||
FASTLANE_USER: eatvio2023@icloud.com
|
||||
FASTLANE_PASSWORD: EeChahboo9Ei
|
||||
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: iqsm-pfxm-bpmg-nkry
|
||||
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
|
||||
@@ -16,6 +16,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Create key.properties file
|
||||
run: |
|
||||
cat > android/key.properties <<EOF
|
||||
storePassword=${{ vars.SIGNING_STORE_PASSWORD }}
|
||||
keyPassword=${{ vars.SIGNING_KEY_PASSWORD }}
|
||||
keyAlias=${{ vars.SIGNING_KEY_ALIAS }}
|
||||
storeFile=upload-keystore.jks
|
||||
EOF
|
||||
|
||||
- name: Decode keystore from secret
|
||||
run: |
|
||||
echo "${{ vars.SIGNING_KEY_JKS }}"
|
||||
printf "%s" "${{ vars.SIGNING_KEY_JKS }}" | base64 -d > android/app/upload-keystore.jks
|
||||
|
||||
- name: Cache Android SDK
|
||||
id: cache-sdk
|
||||
uses: "https://gitea.com/actions/cache@v4"
|
||||
@@ -104,19 +118,6 @@ jobs:
|
||||
- name: Test
|
||||
run: flutter test
|
||||
|
||||
- name: Create key.properties file
|
||||
run: |
|
||||
cat > android/key.properties <<EOF
|
||||
storePassword=${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
keyPassword=${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
keyAlias=${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
storeFile=my-release-key.jks
|
||||
EOF
|
||||
|
||||
- name: Decode keystore from secret
|
||||
run: |
|
||||
printf "%s" "${{ secrets.SIGNING_KEY_JKS }}" | base64 -d > android/app/my-release-key.jks
|
||||
|
||||
- name: Build apk
|
||||
run: flutter build apk --release
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -538,7 +538,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -576,7 +576,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>9</string>
|
||||
<string>15</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -138,7 +138,7 @@ class AppRouter {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed');
|
||||
if (welcomeScreenWasShowed == true) {
|
||||
return '/my-profile-screen';
|
||||
return '/feed-screen';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -196,7 +196,13 @@ class AppRouter {
|
||||
return AboutRecipeScreen(recipe: data['recipe'] as Recipe, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||
},
|
||||
),
|
||||
GoRoute(path: RecipeCreateScreen.path, name: RecipeCreateScreen.name, builder: (context, state) => const RecipeCreateScreen()),
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
name: RecipeCreateScreen.name,
|
||||
builder: (context, state) => RecipeCreateScreen(
|
||||
recipe: state.extra != null ? state.extra as Recipe : null,
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/profile-screen', name: 'profileScreen', builder: (context, state) => ProfileScreen(author: state.extra as Author)),
|
||||
GoRoute(
|
||||
path: '/subs-screen/:slug',
|
||||
@@ -224,7 +230,10 @@ class AppRouter {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final chatId = data['chatId'] as String;
|
||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||
return ChatScreen(chatId: chatId, sendInitialMessage: sendInitialMessage,);
|
||||
return ChatScreen(
|
||||
chatId: chatId,
|
||||
sendInitialMessage: sendInitialMessage,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
|
||||
@@ -107,7 +107,7 @@ Future<void> init() async {
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
|
||||
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||
|
||||
@@ -6,9 +6,10 @@ import 'package:eatvio_app/core/utils/toasts.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
@@ -26,6 +27,7 @@ class CustomPopup extends StatelessWidget {
|
||||
final String? slug;
|
||||
final int? id;
|
||||
final FeedBlogPostModel? feedBlogPostModel;
|
||||
final Recipe? recipe;
|
||||
|
||||
const CustomPopup({
|
||||
super.key,
|
||||
@@ -35,6 +37,7 @@ class CustomPopup extends StatelessWidget {
|
||||
this.id,
|
||||
this.slug,
|
||||
this.feedBlogPostModel,
|
||||
this.recipe,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -94,6 +97,8 @@ class CustomPopup extends StatelessWidget {
|
||||
addItem('assets/icons/edit_paint.svg', 'Редактировать', () {
|
||||
if (complainedContentType == ComplainedContent.posts) {
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedBlogPostModel);
|
||||
} else if (complainedContentType == ComplainedContent.recipe) {
|
||||
GoRouter.of(context).pushNamed(RecipeCreateScreen.name, extra: recipe);
|
||||
} else {
|
||||
showInfoToast('В разработке');
|
||||
return;
|
||||
@@ -103,8 +108,6 @@ class CustomPopup extends StatelessWidget {
|
||||
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () async {
|
||||
if (complainedContentType == ComplainedContent.diet) {
|
||||
final result = await sl<MyDietsCubit>().unpublishDiet(id!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
@@ -112,8 +115,6 @@ class CustomPopup extends StatelessWidget {
|
||||
showSuccessToast('Диета перенесена в архив, обновите ленту');
|
||||
} else if (complainedContentType == ComplainedContent.plan) {
|
||||
final result = await sl<MyPlansCubit>().unpublish(slug!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
@@ -125,8 +126,6 @@ class CustomPopup extends StatelessWidget {
|
||||
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
|
||||
if (complainedContentType == ComplainedContent.posts) {
|
||||
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(id!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
@@ -134,8 +133,6 @@ class CustomPopup extends StatelessWidget {
|
||||
showSuccessToast('Пост удален, обновите ленту');
|
||||
} else if (complainedContentType == ComplainedContent.recipe) {
|
||||
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
@@ -143,8 +140,6 @@ class CustomPopup extends StatelessWidget {
|
||||
showSuccessToast('Рецепт удален, обновите ленту');
|
||||
} else if (complainedContentType == ComplainedContent.diet) {
|
||||
final result = await sl<DietsCubit>().deleteDiet(id!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
@@ -152,8 +147,6 @@ class CustomPopup extends StatelessWidget {
|
||||
showSuccessToast('Диета удалена, обновите ленту');
|
||||
} else if (complainedContentType == ComplainedContent.plan) {
|
||||
final result = await sl<MyPlansCubit>().deletePlan(slug!);
|
||||
if (!context.mounted) return;
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
|
||||
@@ -30,7 +30,7 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
);
|
||||
showSuccessToast('Успешный вход');
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
AppRouter.navigatorKey.currentContext?.go('/my-profile-screen');
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen');
|
||||
return true;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
|
||||
@@ -27,7 +27,7 @@ class BottomNavigationBarWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||
int currentBottomNavBarIndex = 0;
|
||||
int currentBottomNavBarIndex = 1;
|
||||
late final List<String> pages;
|
||||
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'consultation_request_model.freezed.dart';
|
||||
|
||||
part 'consultation_request_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class ConsultationRequestModel with _$ConsultationRequestModel {
|
||||
const factory ConsultationRequestModel({
|
||||
@JsonKey(name: 'rating_place') required int ratingPlace,
|
||||
@JsonKey(name: 'education_resource') required EducationModel educationModel,
|
||||
@JsonKey(name: 'cert_photos') required List<CertPhotosModel> photos,
|
||||
}) = _ConsultationRequestModel;
|
||||
|
||||
factory ConsultationRequestModel.fromJson(Map<String, dynamic> json) => _$ConsultationRequestModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CertPhotosModel with _$CertPhotosModel {
|
||||
const factory CertPhotosModel({
|
||||
required String url,
|
||||
}) = _CertPhotosModel;
|
||||
|
||||
factory CertPhotosModel.fromJson(Map<String, dynamic> json) => _$CertPhotosModelFromJson(json);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -33,7 +34,9 @@ class ConsultantRepository {
|
||||
final fileBytes = await file.readAsBytes();
|
||||
final multipartFile = MultipartFile.fromBytes(
|
||||
fileBytes,
|
||||
filename: file.path.split('/').last,
|
||||
filename: file.path
|
||||
.split('/')
|
||||
.last,
|
||||
contentType: MediaType('application', 'octet-stream'),
|
||||
);
|
||||
multipartFiles.add(multipartFile);
|
||||
@@ -46,4 +49,12 @@ class ConsultantRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async {
|
||||
try {
|
||||
return await consultantService.getConsultantRequestStatus(slug: slug);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'consultant_service.g.dart';
|
||||
@@ -18,4 +19,9 @@ abstract class ConsultantService {
|
||||
Future<List<String>> uploadPhotos({
|
||||
@Part(name: 'photos[]', contentType: 'multipart/form-data') List<MultipartFile>? photos,
|
||||
});
|
||||
|
||||
@GET('/api/consultant_request/{slug}/status')
|
||||
Future<ConsultationRequestModel> getConsultantRequestStatus({
|
||||
@Path() required String slug,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,6 +55,27 @@ enum TypeOfRecipe {
|
||||
}
|
||||
}
|
||||
|
||||
static TypeOfRecipe? fromApiType(String value) {
|
||||
switch (value) {
|
||||
case 'first_meals':
|
||||
return TypeOfRecipe.firstMeals;
|
||||
case 'second_meals':
|
||||
return TypeOfRecipe.secondMeals;
|
||||
case 'salads':
|
||||
return TypeOfRecipe.salads;
|
||||
case 'snacks':
|
||||
return TypeOfRecipe.snacks;
|
||||
case 'sweets':
|
||||
return TypeOfRecipe.sweets;
|
||||
case 'drinks':
|
||||
return TypeOfRecipe.drinks;
|
||||
case 'other':
|
||||
return TypeOfRecipe.other;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static TypeOfRecipe? fromString(String value) {
|
||||
switch (value) {
|
||||
case 'Первые блюда':
|
||||
@@ -80,6 +101,7 @@ enum TypeOfRecipe {
|
||||
@freezed
|
||||
class RecipeCreateEntity with _$RecipeCreateEntity {
|
||||
const RecipeCreateEntity._();
|
||||
|
||||
const factory RecipeCreateEntity({
|
||||
String? title,
|
||||
TypeOfRecipe? typeOfRecipe,
|
||||
@@ -94,7 +116,7 @@ class RecipeCreateEntity with _$RecipeCreateEntity {
|
||||
String? videoUrl,
|
||||
required List<Ingredient> ingredients,
|
||||
required List<StepEntity> steps,
|
||||
required List<String> tags, // tags for search
|
||||
required List<String> tags,
|
||||
}) = _RecipeCreateEntity;
|
||||
|
||||
RecipeModel toModel() => RecipeModel(
|
||||
@@ -118,6 +140,7 @@ class RecipeCreateEntity with _$RecipeCreateEntity {
|
||||
@freezed
|
||||
class StepEntity with _$StepEntity {
|
||||
const StepEntity._();
|
||||
|
||||
const factory StepEntity({
|
||||
String? description,
|
||||
File? image,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart' as i;
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
final RecipeRepository recipeRepository;
|
||||
@@ -17,6 +21,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
required this.glossaryRepository,
|
||||
}) : super(
|
||||
const CreateRecipeState(
|
||||
loading: true,
|
||||
entity: RecipeCreateEntity(
|
||||
typeOfRecipe: TypeOfRecipe.firstMeals,
|
||||
ingredients: [],
|
||||
@@ -43,7 +48,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
emit(state.copyWith.entity(steps: steps));
|
||||
}
|
||||
|
||||
addIngredient(Ingredient entity) {
|
||||
addIngredient(i.Ingredient entity) {
|
||||
emit(
|
||||
state.copyWith.entity(
|
||||
ingredients: [
|
||||
@@ -55,20 +60,20 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
}
|
||||
|
||||
removeIngredient(int index) {
|
||||
List<Ingredient> ingredients = [...state.entity.ingredients];
|
||||
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
||||
ingredients.removeAt(index);
|
||||
|
||||
emit(state.copyWith.entity(ingredients: ingredients));
|
||||
}
|
||||
|
||||
void updateIngredient({required int index, required Ingredient ingredient}) {
|
||||
void updateIngredient({required int index, required i.Ingredient ingredient}) {
|
||||
final ingredients = [...state.entity.ingredients];
|
||||
ingredients[index] = ingredient;
|
||||
emit(state.copyWith.entity(ingredients: ingredients));
|
||||
}
|
||||
|
||||
replaceIngredientPosition(int oldIndex, int newIndex) {
|
||||
List<Ingredient> ingredients = [...state.entity.ingredients];
|
||||
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
||||
final item = ingredients.removeAt(oldIndex);
|
||||
ingredients.insert(newIndex, item);
|
||||
emit(state.copyWith.entity(ingredients: ingredients));
|
||||
@@ -152,10 +157,10 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
emit(state.copyWith.entity(steps: steps));
|
||||
}
|
||||
|
||||
Future<bool> publishRecipe() async {
|
||||
Future<bool> publishRecipe([bool update = false, String? slug]) async {
|
||||
try {
|
||||
final model = state.entity.toModel();
|
||||
await recipeRepository.publishRecipe(model);
|
||||
await recipeRepository.publishRecipe(model, update, slug);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
@@ -163,6 +168,64 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> downloadImageAsFile(String imageUrl) async {
|
||||
final response = await http.get(Uri.parse(imageUrl));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
|
||||
final file = File(filePath);
|
||||
return await file.writeAsBytes(response.bodyBytes);
|
||||
} else {
|
||||
throw Exception('Не удалось загрузить изображение');
|
||||
}
|
||||
}
|
||||
|
||||
disableLoading() {
|
||||
emit(state.copyWith(loading: false));
|
||||
}
|
||||
|
||||
Future<void> recipeToEntity(Recipe recipe) async {
|
||||
final file = await downloadImageAsFile('$baseUrl/storage/${recipe.image}');
|
||||
|
||||
final List<StepEntity> steps = await Future.wait(
|
||||
recipe.steps!.map(
|
||||
(e) async => StepEntity(
|
||||
description: e.description,
|
||||
image: await downloadImageAsFile('$baseUrl/storage/${e.image}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
entity: RecipeCreateEntity(
|
||||
title: recipe.title,
|
||||
typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!),
|
||||
countOfPortions: recipe.servings,
|
||||
energyInOnePortion: recipe.calories,
|
||||
totalCookHours: recipe.cookingTime ~/ 60,
|
||||
totalCookMinutes: recipe.cookingTime % 60,
|
||||
activeCookHours: recipe.activeCookingTime ~/ 60,
|
||||
activeCookMinutes: recipe.activeCookingTime % 60,
|
||||
description: recipe.description,
|
||||
image: file,
|
||||
videoUrl: recipe.videoUrl,
|
||||
ingredients: recipe.ingredients!
|
||||
.map((e) => i.Ingredient(
|
||||
name: e.name,
|
||||
unit: e.unit!,
|
||||
portion: e.portion,
|
||||
productId: e.productId,
|
||||
))
|
||||
.toList(),
|
||||
steps: steps,
|
||||
tags: recipe.tags.map((e) => (e.name)).toList(),
|
||||
),
|
||||
loading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool validateAll() {
|
||||
final isTitleValid = validateTitle();
|
||||
final isDescValid = validateDescription();
|
||||
@@ -251,6 +314,15 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteRecipe(String slug) async {
|
||||
try {
|
||||
await recipeRepository.deleteRecipe(slug);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
|
||||
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ part 'create_recipe_state.freezed.dart';
|
||||
class CreateRecipeState with _$CreateRecipeState {
|
||||
const factory CreateRecipeState({
|
||||
required RecipeCreateEntity entity,
|
||||
required bool loading,
|
||||
String? titleValidationError,
|
||||
String? descriptionValidationError,
|
||||
String? cookingTimeValidationError,
|
||||
@@ -15,5 +16,5 @@ class CreateRecipeState with _$CreateRecipeState {
|
||||
String? energyPerServingValidationError,
|
||||
String? imageValidationError,
|
||||
}) = _CreateRecipeState;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/core/widgets/success_published_recipe_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
@@ -8,13 +9,19 @@ import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recip
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/widgets/ingredients_widget.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/widgets/steps_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class RecipeCreateScreen extends StatefulWidget {
|
||||
const RecipeCreateScreen({super.key});
|
||||
final Recipe? recipe;
|
||||
|
||||
const RecipeCreateScreen({
|
||||
super.key,
|
||||
this.recipe,
|
||||
});
|
||||
|
||||
static const name = 'recipeCreateScreen';
|
||||
static const path = '/recipe-create-screen';
|
||||
@@ -24,85 +31,115 @@ class RecipeCreateScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = sl<CreateRecipeCubit>();
|
||||
if (widget.recipe != null) {
|
||||
createRecipeCubit.recipeToEntity(widget.recipe!);
|
||||
} else {
|
||||
createRecipeCubit.disableLoading();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return BlocProvider<CreateRecipeCubit>(
|
||||
create: (BuildContext context) => sl<CreateRecipeCubit>(),
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF0F2F5),
|
||||
appBar: AppBar(
|
||||
title: const Text('Добавление рецепта'),
|
||||
centerTitle: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 32),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
const InfoAboutRecipeWidget(),
|
||||
const IngredientsWidget(),
|
||||
const StepsWidget(),
|
||||
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
builder: (context, state) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
if (context.read<CreateRecipeCubit>().validateAll()) {
|
||||
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe();
|
||||
if (isSuccess) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedRecipeBottomSheet(
|
||||
title: state.entity.title ?? 'Рецепт',
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
|
||||
}
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'Опубликовать',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
create: (BuildContext context) => createRecipeCubit,
|
||||
child: BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
builder: (BuildContext context, CreateRecipeState state) {
|
||||
if (state.loading) {
|
||||
return const Scaffold(
|
||||
body: Loader(),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF0F2F5),
|
||||
appBar: AppBar(
|
||||
title: widget.recipe != null ? const Text('Редактирование рецепта') : const Text('Добавление рецепта'),
|
||||
centerTitle: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 32),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
InfoAboutRecipeWidget(
|
||||
createRecipeCubit: createRecipeCubit,
|
||||
),
|
||||
const IngredientsWidget(),
|
||||
StepsWidget(
|
||||
createRecipeCubit: createRecipeCubit,
|
||||
),
|
||||
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
builder: (context, state) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
if (context.read<CreateRecipeCubit>().validateAll()) {
|
||||
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe(
|
||||
widget.recipe == null ? false : true,
|
||||
widget.recipe?.slug,
|
||||
);
|
||||
if (isSuccess) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedRecipeBottomSheet(
|
||||
title: state.entity.title ?? 'Рецепт',
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
|
||||
}
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
widget.recipe != null ? 'Обновить' : 'Опубликовать',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class InfoAboutRecipeWidget extends StatefulWidget {
|
||||
const InfoAboutRecipeWidget({super.key});
|
||||
final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
const InfoAboutRecipeWidget({super.key, required this.createRecipeCubit});
|
||||
|
||||
@override
|
||||
State<InfoAboutRecipeWidget> createState() => _InfoAboutRecipeWidgetState();
|
||||
@@ -23,13 +25,19 @@ class InfoAboutRecipeWidget extends StatefulWidget {
|
||||
class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
final List<SelectionPopupModel> items = TypeOfRecipe.values.map((e) => SelectionPopupModel(title: e.toString())).toList();
|
||||
late SelectionPopupModel selectedType;
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedType = items.first;
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
if (widget.createRecipeCubit.state.entity.typeOfRecipe != null) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (items[i].title == widget.createRecipeCubit.state.entity.typeOfRecipe!.toString()) {
|
||||
selectedType = items[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedType = items.first;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -44,6 +52,7 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
const Text('Название'),
|
||||
const SizedBox(height: 4),
|
||||
BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
bloc: widget.createRecipeCubit,
|
||||
buildWhen: (previous, current) => previous.titleValidationError != current.titleValidationError,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
@@ -51,10 +60,11 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: 'Например: тар-тар из говядины',
|
||||
hasError: state.titleValidationError != null,
|
||||
initialValue: state.entity.title,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceTitle(value);
|
||||
widget.createRecipeCubit.replaceTitle(value);
|
||||
if (state.titleValidationError != null) {
|
||||
createRecipeCubit.validateTitle();
|
||||
widget.createRecipeCubit.validateTitle();
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -78,7 +88,7 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
setState(() {
|
||||
selectedType = newSelectedType;
|
||||
});
|
||||
createRecipeCubit.changeType(
|
||||
widget.createRecipeCubit.changeType(
|
||||
TypeOfRecipe.fromString(selectedType.title) ?? TypeOfRecipe.other,
|
||||
);
|
||||
},
|
||||
@@ -100,12 +110,13 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.',
|
||||
minLines: 5,
|
||||
initialValue: state.entity.description,
|
||||
maxLines: 5,
|
||||
hasError: state.descriptionValidationError != null,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceDescription(value);
|
||||
widget.createRecipeCubit.replaceDescription(value);
|
||||
if (state.descriptionValidationError != null) {
|
||||
createRecipeCubit.validateDescription();
|
||||
widget.createRecipeCubit.validateDescription();
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -148,8 +159,9 @@ class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Прикрепите ссылку на видео',
|
||||
initialValue: widget.createRecipeCubit.state.entity.videoUrl,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceVideoUrl(value);
|
||||
widget.createRecipeCubit.replaceVideoUrl(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@ class PortionWidget extends StatefulWidget {
|
||||
|
||||
class _PortionWidgetState extends State<PortionWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -45,6 +46,7 @@ class _PortionWidgetState extends State<PortionWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: '1',
|
||||
hasError: state.servingsValidationError != null,
|
||||
initialValue: state.entity.countOfPortions?.toString(),
|
||||
textInputType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replacePortionsCount(int.parse(value));
|
||||
@@ -77,6 +79,7 @@ class _PortionWidgetState extends State<PortionWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: '100',
|
||||
hasError: state.energyPerServingValidationError != null,
|
||||
initialValue: state.entity.energyInOnePortion?.toString(),
|
||||
suffixText: 'Ккал',
|
||||
textInputType: const TextInputType.numberWithOptions(decimal: true),
|
||||
onChanged: (value) {
|
||||
|
||||
@@ -3,16 +3,16 @@ import 'dart:io';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class StepImageWidget extends StatefulWidget {
|
||||
final CreateRecipeCubit createRecipeCubit;
|
||||
final int index;
|
||||
|
||||
const StepImageWidget({
|
||||
super.key,
|
||||
required this.index,
|
||||
required this.index, required this.createRecipeCubit,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -21,12 +21,15 @@ class StepImageWidget extends StatefulWidget {
|
||||
|
||||
class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
XFile? image;
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
if (widget.createRecipeCubit.state.entity.steps.length > widget.index) {
|
||||
if (widget.createRecipeCubit.state.entity.steps[widget.index].image != null) {
|
||||
image = XFile(widget.createRecipeCubit.state.entity.steps[widget.index].image!.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -56,7 +59,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
setState(() {
|
||||
image = null;
|
||||
});
|
||||
createRecipeCubit.replaceImageInStep(null, widget.index);
|
||||
widget.createRecipeCubit.replaceImageInStep(null, widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
@@ -84,7 +87,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
image = await picker.pickImage(source: ImageSource.camera);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
@@ -103,7 +106,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
image = await picker.pickImage(source: ImageSource.gallery);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
widget.createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
|
||||
@@ -7,24 +7,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class StepsWidget extends StatefulWidget {
|
||||
const StepsWidget({super.key});
|
||||
final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
const StepsWidget({super.key, required this.createRecipeCubit});
|
||||
|
||||
@override
|
||||
State<StepsWidget> createState() => _StepsWidgetState();
|
||||
}
|
||||
|
||||
class _StepsWidgetState extends State<StepsWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
bloc: widget.createRecipeCubit,
|
||||
builder: (BuildContext context, CreateRecipeState state) {
|
||||
return Material(
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -83,10 +78,11 @@ class _StepsWidgetState extends State<StepsWidget> {
|
||||
const SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Например: подготовьте ингредиенты',
|
||||
initialValue: widget.createRecipeCubit.state.entity.steps[index].description,
|
||||
maxLines: 5,
|
||||
minLines: 3,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceDescriptionInStep(value, index);
|
||||
widget.createRecipeCubit.replaceDescriptionInStep(value, index);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -94,6 +90,7 @@ class _StepsWidgetState extends State<StepsWidget> {
|
||||
const SizedBox(height: 4),
|
||||
StepImageWidget(
|
||||
index: index,
|
||||
createRecipeCubit: widget.createRecipeCubit,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class TagsWidget extends StatefulWidget {
|
||||
const TagsWidget({super.key});
|
||||
@@ -10,8 +12,16 @@ class TagsWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TagsWidgetState extends State<TagsWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
final tagController = TextEditingController();
|
||||
final List<String> tags = [];
|
||||
late final List<String> tags;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
tags = createRecipeCubit.state.entity.tags;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -71,6 +71,7 @@ class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
|
||||
hintText: 'Часов',
|
||||
hasError: state.cookingTimeValidationError != null,
|
||||
textInputType: TextInputType.number,
|
||||
initialValue: state.entity.totalCookHours?.toString(),
|
||||
onChanged: (value) {
|
||||
final hours = int.tryParse(value);
|
||||
if (hours == null) return;
|
||||
@@ -97,6 +98,7 @@ class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
|
||||
hintText: 'Минут',
|
||||
hasError: state.cookingTimeValidationError != null,
|
||||
textInputType: TextInputType.number,
|
||||
initialValue: state.entity.totalCookMinutes?.toString(),
|
||||
onChanged: (value) {
|
||||
final minutes = int.tryParse(value);
|
||||
if (minutes == null) return;
|
||||
@@ -156,6 +158,7 @@ class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: 'Часов',
|
||||
hasError: state.activeTimeValidationError != null,
|
||||
initialValue: state.entity.activeCookHours?.toString(),
|
||||
textInputType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
final hours = int.tryParse(value);
|
||||
@@ -183,6 +186,7 @@ class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
|
||||
CustomTextFormField(
|
||||
hintText: 'Минут',
|
||||
hasError: state.activeTimeValidationError != null,
|
||||
initialValue: state.entity.activeCookMinutes?.toString(),
|
||||
textInputType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
final minutes = int.tryParse(value);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
@@ -11,7 +10,6 @@ import 'package:eatvio_app/features/create_recipe/models/product.dart';
|
||||
|
||||
class RecipeRepository {
|
||||
final CreateRecipeService recipeService;
|
||||
|
||||
|
||||
RecipeRepository({required this.recipeService});
|
||||
|
||||
@@ -23,7 +21,7 @@ class RecipeRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> publishRecipe(RecipeModel recipe) async {
|
||||
Future<void> publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async {
|
||||
try {
|
||||
if (recipe.image == null || (recipe.image?.isEmpty ?? true)) {
|
||||
return;
|
||||
@@ -72,15 +70,11 @@ class RecipeRepository {
|
||||
};
|
||||
formMap.removeWhere((k, v) => v == null || (v is String && v.isEmpty));
|
||||
final formData = FormData.fromMap(formMap);
|
||||
await recipeService.publishRecipe(formData: formData);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<RecipeModel> editRecipe(RecipeModel recipe, String id) async {
|
||||
try {
|
||||
return await recipeService.editRecipe(jsonDecode(jsonEncode(recipe)), id);
|
||||
if (update) {
|
||||
await recipeService.updateRecipe(slug: slug!, formData: formData);
|
||||
} else {
|
||||
await recipeService.publishRecipe(formData: formData);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
@@ -100,5 +94,13 @@ class RecipeRepository {
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteRecipe(String slug) async {
|
||||
try {
|
||||
return await recipeService.deleteRecipe(slug: slug);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,18 @@ abstract class CreateRecipeService {
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/recipes/update/{id}')
|
||||
Future<RecipeModel> editRecipe(@Part() Map<String, Object?> recipe, String id);
|
||||
|
||||
@GET('/api/recipes/tags')
|
||||
Future<RecipeTagModel> getTags();
|
||||
|
||||
@DELETE('/api/recipes/delete/{slug}')
|
||||
Future<void> deleteRecipe({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
}
|
||||
@POST('/api/recipes/update/{slug}')
|
||||
@MultiPart()
|
||||
Future<void> updateRecipe({
|
||||
@Path() required String slug,
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,21 +25,40 @@ class Recipe with _$Recipe {
|
||||
@JsonKey(name: 'was_liked_by_me') required bool wasLikedByMe,
|
||||
@JsonKey(name: 'is_favorite') required bool isFavorite,
|
||||
@JsonKey(name: 'energy_per_serving') required double calories,
|
||||
int? servings,
|
||||
@JsonKey(name: 'total_likes') required int likesCount,
|
||||
@JsonKey(name: 'total_views') required int viewsCount,
|
||||
required List<Tag> tags,
|
||||
required Author author,
|
||||
}) = _Recipes;
|
||||
|
||||
factory Recipe.fromJson(Map<String, Object?> json) => _$RecipeFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Tag with _$Tag {
|
||||
const factory Tag({required String name}) = _Tag;
|
||||
|
||||
factory Tag.fromJson(Map<String, Object?> json) => _$TagFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Ingredient with _$Ingredient {
|
||||
const factory Ingredient({required String name, String? unit, required String portion}) = _Ingredient;
|
||||
const factory Ingredient({
|
||||
required String name,
|
||||
String? unit,
|
||||
required String portion,
|
||||
@JsonKey(name: 'product_nutritional_id', fromJson: _parseProductId) required int productId,
|
||||
}) = _Ingredient;
|
||||
|
||||
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
|
||||
}
|
||||
|
||||
int _parseProductId(json) {
|
||||
if (json is String) {
|
||||
return int.parse(json);
|
||||
}
|
||||
return json as int;
|
||||
}
|
||||
@freezed
|
||||
class Step with _$Step {
|
||||
const factory Step({required String image, @JsonKey(name: 'description_md') required String description}) = _Step;
|
||||
|
||||
@@ -127,6 +127,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
slug: widget.recipe.slug,
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
recipe: widget.recipe,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -26,7 +26,6 @@ class SubModel with _$SubModel {
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: 'profile_photo_url') required String profilePhotoUrl,
|
||||
required List<String> roles,
|
||||
@JsonKey(name: 'is_public_profile') required bool isPublicProfile,
|
||||
}) = _SubModel;
|
||||
|
||||
factory SubModel.fromJson(Map<String, Object?> json) => _$SubModelFromJson(json);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
@@ -10,20 +11,24 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
class ProfileCubit extends Cubit<ProfileState> {
|
||||
final ProfileRepository profileRepository;
|
||||
final ChatRepository chatRepository;
|
||||
final ConsultantRepository consultantRepository;
|
||||
String _currentPeriod = '1';
|
||||
|
||||
ProfileCubit({
|
||||
required this.profileRepository,
|
||||
required this.chatRepository,
|
||||
required this.consultantRepository,
|
||||
}) : super(const ProfileState.loading());
|
||||
|
||||
Future<void> getProfileBySlug(String slug) async {
|
||||
try {
|
||||
final profile = await profileRepository.getProfileBySlug(slug);
|
||||
emit(
|
||||
ProfileState.loaded(
|
||||
profile: await profileRepository.getProfileBySlug(slug),
|
||||
profile: profile,
|
||||
profileDetails: await profileRepository.getProfileDetailsBySlug(slug),
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug),
|
||||
consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(slug) : null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -44,6 +49,7 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug),
|
||||
weightPoints: await profileRepository.getWeight(period: period),
|
||||
circumferencePoints: await profileRepository.getCircumferences(period: period),
|
||||
consultantInfo: profile.roles.contains('consultant') ? await consultantRepository.getConsultantRequestStatus(profile.slug) : null,
|
||||
),
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
@@ -18,6 +19,7 @@ abstract class ProfileState with _$ProfileState {
|
||||
required List<FeedBlogPostModel> blogPosts,
|
||||
List<WeightModel>? weightPoints,
|
||||
List<CircumferenceModel>? circumferencePoints,
|
||||
ConsultationRequestModel? consultantInfo,
|
||||
}) = ProfileStateLoaded;
|
||||
|
||||
const factory ProfileState.error({
|
||||
|
||||
@@ -93,7 +93,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
} else if (state is ProfileStateError) {
|
||||
return DataErrorWidget(
|
||||
onReload: () async {
|
||||
widget.author == null || widget.author!.authorIsMe ? await profileCubit.getMyProfile() : await profileCubit.getProfileBySlug(widget.author!.authorSlug);
|
||||
widget.author == null || widget.author!.authorIsMe
|
||||
? await profileCubit.getMyProfile()
|
||||
: await profileCubit.getProfileBySlug(widget.author!.authorSlug);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -144,25 +146,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (state.profile.ratingPlace != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF6FAFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${state.profile.ratingPlace!}-й в рейтинге',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF6FAFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${state.consultantInfo!.ratingPlace}-й в рейтинге',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state.profile.educationResource?.hasMedicalEducation == 1)
|
||||
if (state.consultantInfo!.educationModel.hasMedicalEducation == 1)
|
||||
const Row(
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
@@ -221,6 +222,61 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_header(state),
|
||||
if (state.profile.roles.contains('consultant'))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
const Text(
|
||||
'Консультант',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(
|
||||
0xFF323F4B,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF6FAFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${state.consultantInfo!.ratingPlace}-й в рейтинге',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state.consultantInfo!.educationModel.hasMedicalEducation == 1)
|
||||
const Row(
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Icon(
|
||||
Icons.done_rounded,
|
||||
color: Colors.green,
|
||||
),
|
||||
Text(
|
||||
'Медицинское образование',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state),
|
||||
],
|
||||
),
|
||||
@@ -371,7 +427,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
}
|
||||
|
||||
_diplomasAndCertificates(ProfileStateLoaded state) {
|
||||
if (!state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) {
|
||||
if (!state.profile.roles.contains('consultant') || state.consultantInfo!.photos.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
@@ -416,8 +472,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (state.profile.diplomasAndCertificates != null)
|
||||
...state.profile.diplomasAndCertificates!.map((d) => _diplomaImage('$baseUrl/storage/${d.path}')),
|
||||
if (state.consultantInfo!.photos.isNotEmpty)
|
||||
...List.generate(
|
||||
state.consultantInfo!.photos.length * 2 - 1,
|
||||
(index) {
|
||||
if (index.isEven) {
|
||||
final photo = state.consultantInfo!.photos[index ~/ 2];
|
||||
return _diplomaImage(photo.url);
|
||||
} else {
|
||||
return const SizedBox(width: 8);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -456,8 +522,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
|
||||
_education(ProfileStateLoaded state) {
|
||||
if (!state.profile.roles.contains('consultant') ||
|
||||
state.profile.educationResource?.completedCourses == null ||
|
||||
(state.profile.educationResource?.completedCourses?.isEmpty ?? false)) {
|
||||
state.consultantInfo!.educationModel.completedCourses == null ||
|
||||
(state.consultantInfo!.educationModel.completedCourses?.isEmpty ?? false)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
@@ -495,11 +561,17 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.profile.educationResource?.completedCourses != null)
|
||||
...state.profile.educationResource!.completedCourses!.map((c) => _educationBlock(c.values.first.title, c.values.first.date)),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _educationBlock(state.consultantInfo!.educationModel.completedCourses![index].values.first.title,
|
||||
state.consultantInfo!.educationModel.completedCourses![index].values.first.date),
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||
itemCount: state.consultantInfo!.educationModel.completedCourses!.length,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -564,39 +636,39 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.vkLink ?? '',
|
||||
state.consultantInfo!.educationModel.vkLink ?? '',
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
'assets/icons/vk.svg',
|
||||
state.profile.educationResource?.vkLink == null ? true : false,
|
||||
state.consultantInfo!.educationModel.vkLink!.isEmpty ? true : false,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.vkLink ?? '',
|
||||
state.consultantInfo!.educationModel.tgLink ?? '',
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
'assets/icons/telegram.svg',
|
||||
state.profile.educationResource?.vkLink == null ? true : false,
|
||||
state.consultantInfo!.educationModel.tgLink!.isEmpty ? true : false,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.personalSiteLink ?? '',
|
||||
state.consultantInfo!.educationModel.personalSiteLink ?? '',
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
'assets/icons/web.svg',
|
||||
state.profile.educationResource?.personalSiteLink == null ? true : false,
|
||||
state.consultantInfo!.educationModel.personalSiteLink!.isEmpty ? true : false,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -620,7 +692,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
child: SvgPicture.asset(
|
||||
icon,
|
||||
width: 24,
|
||||
colorFilter: const ColorFilter.mode(Colors.black12, BlendMode.srcIn),
|
||||
colorFilter: disabled ? const ColorFilter.mode(Colors.black12, BlendMode.srcIn) : const ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -659,7 +731,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
for (int i = 0; i < state.profile.badges.length; i++) ...[
|
||||
state.profile.badges[i].image.contains('svg')
|
||||
? Tooltip(
|
||||
message: state.profile.badges[i].description,
|
||||
message: state.profile.badges[i].description,
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
textStyle: const TextStyle(
|
||||
color: Colors.black,
|
||||
|
||||
@@ -218,6 +218,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
slug: widget.recipe.slug,
|
||||
recipe: widget.recipe,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
@@ -401,6 +402,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
slug: widget.recipe.slug,
|
||||
recipe: widget.recipe,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
@@ -425,6 +427,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
path: 'recipe/${widget.recipe.slug}',
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
slug: widget.recipe.slug,
|
||||
recipe: widget.recipe,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
|
||||
@@ -57,7 +57,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
|
||||
),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
AppRouter.navigatorKey.currentContext?.go('/my-profile-screen');
|
||||
AppRouter.navigatorKey.currentContext?.go('/feed-screen');
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ class UpdateScreen extends StatelessWidget {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Positioned.fill(
|
||||
Positioned.fill(
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
|
||||
@@ -37,8 +37,8 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Positioned.fill(
|
||||
Positioned.fill(
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
@@ -80,7 +80,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
onTap: () async {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
await sharedPreferences.setBool('welcomeScreenWasShowed', true);
|
||||
goRouter.go('/my-profile-screen');
|
||||
goRouter.go('/feed-screen');
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Ink(
|
||||
|
||||
@@ -35,7 +35,7 @@ void main() {
|
||||
|
||||
test('getProfileDetailsBySlug', () async {
|
||||
when(mockProfileService.getProfileDetailsBySlug(slug: 'slug')).thenAnswer(
|
||||
(_) async => await Future.value(
|
||||
(_) async => await Future.value(
|
||||
const ProfileDetailsModel(
|
||||
firstName: 'Alexnader',
|
||||
lastName: 'Avanesyan',
|
||||
@@ -70,21 +70,21 @@ void main() {
|
||||
|
||||
test('getBlogPostByAuthorSlug', () async {
|
||||
when(mockProfileService.getBlogPostByAuthorSlug(slug: '')).thenAnswer(
|
||||
(_) async => [],
|
||||
(_) async => [],
|
||||
);
|
||||
expect(await mockProfileService.getBlogPostByAuthorSlug(slug: 'slug'), isA<List<FeedBlogPostModel>>());
|
||||
});
|
||||
|
||||
test('getWeight', () async {
|
||||
when(mockProfileService.getWeight(period: '')).thenAnswer(
|
||||
(_) async => [],
|
||||
(_) async => [],
|
||||
);
|
||||
expect(await mockProfileService.getWeight(period: ''), isA<List<WeightModel>>());
|
||||
});
|
||||
|
||||
test('getCircumferences', () async {
|
||||
when(mockProfileService.getCircumferences(period: '')).thenAnswer(
|
||||
(_) async => [],
|
||||
(_) async => [],
|
||||
);
|
||||
expect(await mockProfileService.getCircumferences(period: ''), isA<List<CircumferenceModel>>());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user