forked from eatvio/eatvio_app
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
4
android/.kotlin/errors/errors-1733227862976.log
Normal file
4
android/.kotlin/errors/errors-1733227862976.log
Normal file
@@ -0,0 +1,4 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||
1. Kotlin compile daemon is ready
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart';
|
||||
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
||||
@@ -19,7 +20,6 @@ import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.d
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.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/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart';
|
||||
@@ -41,7 +41,6 @@ import 'package:eatvio_app/features/settings/presentation/pages/physical_form_sc
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AppRouter {
|
||||
@@ -78,7 +77,7 @@ class AppRouter {
|
||||
path: '/my-profile-screen',
|
||||
name: 'myProfileScreen',
|
||||
redirect: (context, state) async {
|
||||
await context.read<InitializationCubit>().runInitialization();
|
||||
await sl<InitializationCubit>().runInitialization();
|
||||
return null;
|
||||
},
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: ProfileScreen()),
|
||||
@@ -167,13 +166,15 @@ class AppRouter {
|
||||
path: '/blog-post-create-screen',
|
||||
name: 'blogPostCreateScreen',
|
||||
builder: (context, state) => BlogPostCreateScreen(
|
||||
feedCubit: state.extra != null ? state.extra as FeedCubit : null,
|
||||
feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blog-post-screen',
|
||||
name: 'blogPostScreen',
|
||||
builder: (context, state) => BlogPostScreen(blogPost: state.extra as FeedBlogPostModel),
|
||||
builder: (context, state) => BlogPostScreen(
|
||||
blogPost: state.extra as FeedBlogPostModel,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
@@ -273,7 +274,7 @@ class AppRouter {
|
||||
path: DietPreviewScreen.path,
|
||||
name: DietPreviewScreen.name,
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Diet;
|
||||
final data = state.extra as Diet;
|
||||
return DietPreviewScreen(
|
||||
diet: data,
|
||||
);
|
||||
|
||||
@@ -58,15 +58,15 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<InitializationCubit>(() => InitializationCubit(chatRepository: sl()));
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit());
|
||||
sl.registerLazySingleton<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
|
||||
// Cubit factories
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl(), initializationCubit: sl()));
|
||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
sl.registerFactory<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
sl.registerFactory<RecipeCreateCubit>(() => RecipeCreateCubit(recipeRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -191,6 +193,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
final token = res['token'];
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
GoRouter.of(context).go('/my-profile-screen');
|
||||
sl<ProfileAvatarCubit>().getMyProfile();
|
||||
} else {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
||||
@@ -19,7 +20,10 @@ import 'package:share_plus/share_plus.dart';
|
||||
class BlogPostScreen extends StatefulWidget {
|
||||
final FeedBlogPostModel blogPost;
|
||||
|
||||
BlogPostScreen({super.key, required this.blogPost});
|
||||
BlogPostScreen({
|
||||
super.key,
|
||||
required this.blogPost,
|
||||
});
|
||||
|
||||
@override
|
||||
State<BlogPostScreen> createState() => _BlogPostScreenState();
|
||||
@@ -185,6 +189,40 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(widget.blogPost.title),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
onSelected: (value) {
|
||||
if (value == 'delete') {
|
||||
sl<BlogPostCreateCubit>().deleteBlogPost(widget.blogPost.id);
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
if (value == 'edit') {
|
||||
GoRouter.of(context).replaceNamed(
|
||||
'blogPostCreateScreen',
|
||||
extra: widget.blogPost,
|
||||
);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
if (widget.blogPost.author.authorIsMe)
|
||||
PopupMenuItem(
|
||||
value: 'edit',
|
||||
child: Text('Редактировать'),
|
||||
),
|
||||
if (widget.blogPost.author.authorIsMe)
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
if (!widget.blogPost.author.authorIsMe)
|
||||
PopupMenuItem(
|
||||
value: 'report',
|
||||
child: Text('Пожаловаться'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: 80),
|
||||
@@ -266,7 +304,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
isLiked
|
||||
? await context.read<FeedCubit>().likePost(widget.blogPost.id)
|
||||
: await context.read<FeedCubit>().unlikedPost(widget.blogPost.id);
|
||||
await context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
sl<FeedCubit>().updateFeedBlogPostList(widget.blogPost, isLiked);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
@@ -318,8 +356,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
if (state is CommentsStateLoading)
|
||||
const Loader(),
|
||||
if (state is CommentsStateLoading) const Loader(),
|
||||
if (state is CommentsStateLoaded)
|
||||
CommentsWidget(
|
||||
id: widget.blogPost.id.toString(),
|
||||
|
||||
@@ -25,4 +25,28 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
}
|
||||
|
||||
updateBlogPost({
|
||||
required int id,
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
String? description,
|
||||
XFile? image,
|
||||
String? oldImageUrl,
|
||||
}) async {
|
||||
emit(BlogPostCreateState.loading());
|
||||
await blogPostRepository.updateBlogPost(
|
||||
id,
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
oldImageUrl,
|
||||
);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
}
|
||||
|
||||
deleteBlogPost(int id) async {
|
||||
await blogPostRepository.deleteBlogPost(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
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/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class BlogPostCreateScreen extends StatefulWidget {
|
||||
final FeedCubit? feedCubit;
|
||||
final FeedBlogPostModel? feedBlogPostModel;
|
||||
|
||||
const BlogPostCreateScreen({
|
||||
required this.feedCubit,
|
||||
this.feedBlogPostModel,
|
||||
}) : super();
|
||||
|
||||
@override
|
||||
@@ -44,6 +45,11 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
shrinkImage = visible;
|
||||
});
|
||||
});
|
||||
if (widget.feedBlogPostModel != null) {
|
||||
titleController.text = widget.feedBlogPostModel!.title;
|
||||
descriptionController.text = widget.feedBlogPostModel!.description;
|
||||
videoUrlController.text = widget.feedBlogPostModel!.videoUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -58,7 +64,8 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
child: BlocListener<BlogPostCreateCubit, BlogPostCreateState>(
|
||||
listener: (BuildContext context, BlogPostCreateState state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
GoRouter.of(context).pop();
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: _buildBlogPostCreate(),
|
||||
@@ -141,13 +148,23 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
await blogPostCubit.createBlogPost(
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
videoUrl: videoUrlController.text,
|
||||
image: image,
|
||||
);
|
||||
widget.feedCubit?.loadFeedBlogPostList();
|
||||
if (widget.feedBlogPostModel != null) {
|
||||
await blogPostCubit.updateBlogPost(
|
||||
id: widget.feedBlogPostModel!.id,
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
videoUrl: videoUrlController.text,
|
||||
image: image,
|
||||
oldImageUrl: widget.feedBlogPostModel!.image,
|
||||
);
|
||||
} else {
|
||||
await blogPostCubit.createBlogPost(
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
videoUrl: videoUrlController.text,
|
||||
image: image,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -175,9 +192,9 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
if (videoUrlController.text.isEmpty) SizedBox(height: 8),
|
||||
if (videoUrlController.text.isEmpty) _buildAddPhoto(),
|
||||
SizedBox(height: 16),
|
||||
if (image == null) Text('Ссылка на видео'),
|
||||
if (image == null) SizedBox(height: 8),
|
||||
if (image == null)
|
||||
if (image == null && widget.feedBlogPostModel?.image == null) Text('Ссылка на видео'),
|
||||
if (image == null && widget.feedBlogPostModel?.image == null) SizedBox(height: 8),
|
||||
if (image == null && widget.feedBlogPostModel?.image == null)
|
||||
CustomTextFormField(
|
||||
controller: videoUrlController,
|
||||
),
|
||||
@@ -195,7 +212,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
if (image == null)
|
||||
if (image == null && widget.feedBlogPostModel?.image == null)
|
||||
AnimatedContainer(
|
||||
height: shrinkImage ? 52 : 128,
|
||||
curve: Curves.easeIn,
|
||||
@@ -233,6 +250,16 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.feedBlogPostModel?.image != null && image == null)
|
||||
Container(
|
||||
width: shrinkImage ? 52 : MediaQuery.of(context).size.width - 32,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Image(
|
||||
image: NetworkImage(baseUrl + '/storage/' + widget.feedBlogPostModel!.image),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class BlogPostRepository {
|
||||
final BlogPostService blogPostService;
|
||||
@@ -29,4 +32,58 @@ class BlogPostRepository {
|
||||
formData: formData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<XFile?> downloadImage(String imageUrl) async {
|
||||
try {
|
||||
// Загрузка изображения
|
||||
final response = await http.get(Uri.parse(baseUrl + '/storage/' + imageUrl));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Получение временной директории
|
||||
final directory = await getTemporaryDirectory();
|
||||
// Создание файла в временной директории
|
||||
final filePath = '${directory.path}/image.jpg'; // Вы можете изменить название файла
|
||||
final file = File(filePath);
|
||||
// Запись данных в файл
|
||||
await file.writeAsBytes(response.bodyBytes);
|
||||
|
||||
// Возвращаем объект XFile
|
||||
return XFile(file.path);
|
||||
} else {
|
||||
print('Ошибка загрузки изображения: ${response.statusCode}');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Произошла ошибка: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateBlogPost(
|
||||
int id,
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
String? description,
|
||||
XFile? image,
|
||||
String? oldImageUrl,
|
||||
) async {
|
||||
FormData formData = FormData.fromMap({
|
||||
'title': title,
|
||||
'description': description,
|
||||
'state': 'active',
|
||||
'video_url': videoUrl,
|
||||
'image': image != null
|
||||
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
|
||||
: oldImageUrl != null
|
||||
? await MultipartFile.fromFile((await downloadImage(oldImageUrl))?.path ?? '', filename: oldImageUrl.split('/').last)
|
||||
: null,
|
||||
});
|
||||
|
||||
return await blogPostService.updateBlogPost(
|
||||
id: id,
|
||||
formData: formData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteBlogPost(int id) async => await blogPostService.deleteBlogPost(id: id);
|
||||
}
|
||||
|
||||
@@ -12,4 +12,16 @@ abstract class BlogPostService {
|
||||
Future<void> createBlogPost({
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/blogPosts/update/{id}')
|
||||
@MultiPart()
|
||||
Future<void> updateBlogPost({
|
||||
@Path('id') required int id,
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/blogPosts/delete/{id}')
|
||||
Future<void> deleteBlogPost({
|
||||
@Path('id') required int id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,6 +47,60 @@ class _BlogPostService implements BlogPostService {
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBlogPost({
|
||||
required int id,
|
||||
required FormData formData,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = formData;
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/blogPosts/update/${id}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteBlogPost({required int id}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/blogPosts/delete/${id}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
@@ -10,10 +10,9 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
||||
}) : super(const ProfileAvatarState.loading());
|
||||
|
||||
getMyProfile() async {
|
||||
emit(const ProfileAvatarState.loading());
|
||||
try {
|
||||
emit(ProfileAvatarState.loaded(
|
||||
profile: await profileRepository.getMyProfile(),
|
||||
),);
|
||||
emit(ProfileAvatarState.loaded(profile: await profileRepository.getMyProfile()));
|
||||
} catch (e) {
|
||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/noti
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -72,22 +71,19 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
label: '',
|
||||
icon: BlocProvider<ProfileAvatarCubit>(
|
||||
create: (context) => sl<ProfileAvatarCubit>()..getMyProfile(),
|
||||
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
radius: 16,
|
||||
);
|
||||
}
|
||||
icon: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
|
||||
builder: (BuildContext context, ProfileAvatarState state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
return CircleAvatar(
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
radius: 16,
|
||||
backgroundColor: Colors.black12,
|
||||
);
|
||||
},
|
||||
),
|
||||
}
|
||||
return CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Colors.black12,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
|
||||
@@ -14,6 +14,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
final ProfileRepository profileRepository;
|
||||
final GlossaryRepository glossaryRepository;
|
||||
static const _dietStateValues = ['active', 'personal', 'draft'];
|
||||
|
||||
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
|
||||
|
||||
void setDetails(String text) {
|
||||
@@ -238,7 +239,13 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
),
|
||||
details: state.dietData.details ?? '',
|
||||
dailySchedule: state.dietData.dailySchedule ?? [],
|
||||
author: Author(authorSlug: authorProfile.slug, authorName: authorProfile.name, authorProfileIcon: authorProfile.profilePhotoUrl, authorIsMe: false),
|
||||
author: Author(
|
||||
authorSlug: authorProfile.slug,
|
||||
authorName: authorProfile.name,
|
||||
authorProfileIcon: authorProfile.profilePhotoUrl,
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
|
||||
@@ -86,7 +86,7 @@ class _AddProductModalState extends State<AddProductModal> {
|
||||
hintText: 'Введите название продукта/блюда',
|
||||
onChanged: (query) {
|
||||
EasyDebounce.debounce(
|
||||
'client-typing',
|
||||
'glossary-search',
|
||||
Duration(milliseconds: 250),
|
||||
() async {
|
||||
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
|
||||
@@ -133,6 +133,7 @@ class _AddProductModalState extends State<AddProductModal> {
|
||||
});
|
||||
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(glossariesList[i].name);
|
||||
setState(() {});
|
||||
FocusManager.instance.primaryFocus?.nextFocus();
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
|
||||
@@ -20,19 +20,31 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'Ваш КБЖУ от дневной\nсуточной нормы',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 18,
|
||||
height: 1,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text(
|
||||
'График отображает сколько калорий вам нужно ежедневно для похуденияи поддержания веса, согласно формуле Харриса-Бенедикта.\nИзменить коэффициент физической нагрузки можно в настройках.',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF323F4B),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.help_outline_rounded,
|
||||
color: appTheme.blueA400,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
const SizedBox(height: 12),
|
||||
Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
@@ -60,7 +72,9 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
RangePointer(
|
||||
value: (state.nutrition.dailyNutritionIntake.carbohydrates / state.nutrition.dailyNutritionRecommendation.carbohydrates) * 100,
|
||||
width: 0.16,
|
||||
color: state.nutrition.dailyNutritionIntake.carbohydrates < state.nutrition.dailyNutritionRecommendation.carbohydrates ? appTheme.blueA400 : Colors.red,
|
||||
color: state.nutrition.dailyNutritionIntake.carbohydrates < state.nutrition.dailyNutritionRecommendation.carbohydrates
|
||||
? appTheme.blueA400
|
||||
: Colors.red,
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
enableAnimation: true,
|
||||
cornerStyle: CornerStyle.bothCurve,
|
||||
@@ -78,7 +92,9 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
RangePointer(
|
||||
value: (state.nutrition.dailyNutritionIntake.fats / state.nutrition.dailyNutritionRecommendation.fats) * 100,
|
||||
width: 0.16,
|
||||
color: state.nutrition.dailyNutritionIntake.fats < state.nutrition.dailyNutritionRecommendation.fats ? appTheme.blueA400 : Colors.red,
|
||||
color: state.nutrition.dailyNutritionIntake.fats < state.nutrition.dailyNutritionRecommendation.fats
|
||||
? appTheme.blueA400
|
||||
: Colors.red,
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
enableAnimation: true,
|
||||
cornerStyle: CornerStyle.bothCurve,
|
||||
@@ -97,7 +113,9 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
RangePointer(
|
||||
value: (state.nutrition.dailyNutritionIntake.proteins / state.nutrition.dailyNutritionRecommendation.proteins) * 100,
|
||||
width: 0.16,
|
||||
color: state.nutrition.dailyNutritionIntake.proteins < state.nutrition.dailyNutritionRecommendation.proteins ? appTheme.blueA400 : Colors.red,
|
||||
color: state.nutrition.dailyNutritionIntake.proteins < state.nutrition.dailyNutritionRecommendation.proteins
|
||||
? appTheme.blueA400
|
||||
: Colors.red,
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
enableAnimation: true,
|
||||
cornerStyle: CornerStyle.bothCurve,
|
||||
@@ -116,7 +134,9 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
RangePointer(
|
||||
value: (state.nutrition.dailyNutritionIntake.energy / state.nutrition.dailyNutritionRecommendation.energy) * 100,
|
||||
width: 0.16,
|
||||
color: state.nutrition.dailyNutritionIntake.energy < state.nutrition.dailyNutritionRecommendation.energy ? appTheme.blueA400 : Colors.red,
|
||||
color: state.nutrition.dailyNutritionIntake.energy < state.nutrition.dailyNutritionRecommendation.energy
|
||||
? appTheme.blueA400
|
||||
: Colors.red,
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
enableAnimation: true,
|
||||
cornerStyle: CornerStyle.bothCurve,
|
||||
|
||||
@@ -46,6 +46,7 @@ class Author with _$Author {
|
||||
@JsonKey(name: "author_name") required String authorName,
|
||||
@JsonKey(name: "author_profile_icon") required String authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") required bool authorIsMe,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me") required bool isEntitySubscribedByMe,
|
||||
}) = _Author;
|
||||
|
||||
factory Author.fromJson(Map<String, dynamic> json) => _$AuthorFromJson(json);
|
||||
|
||||
@@ -812,6 +812,8 @@ mixin _$Author {
|
||||
String get authorProfileIcon => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "author_is_me")
|
||||
bool get authorIsMe => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Author to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -831,7 +833,9 @@ abstract class $AuthorCopyWith<$Res> {
|
||||
{@JsonKey(name: "author_slug") String authorSlug,
|
||||
@JsonKey(name: "author_name") String authorName,
|
||||
@JsonKey(name: "author_profile_icon") String authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") bool authorIsMe});
|
||||
@JsonKey(name: "author_is_me") bool authorIsMe,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
bool isEntitySubscribedByMe});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -853,6 +857,7 @@ class _$AuthorCopyWithImpl<$Res, $Val extends Author>
|
||||
Object? authorName = null,
|
||||
Object? authorProfileIcon = null,
|
||||
Object? authorIsMe = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
authorSlug: null == authorSlug
|
||||
@@ -871,6 +876,10 @@ class _$AuthorCopyWithImpl<$Res, $Val extends Author>
|
||||
? _value.authorIsMe
|
||||
: authorIsMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -886,7 +895,9 @@ abstract class _$$AuthorImplCopyWith<$Res> implements $AuthorCopyWith<$Res> {
|
||||
{@JsonKey(name: "author_slug") String authorSlug,
|
||||
@JsonKey(name: "author_name") String authorName,
|
||||
@JsonKey(name: "author_profile_icon") String authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") bool authorIsMe});
|
||||
@JsonKey(name: "author_is_me") bool authorIsMe,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
bool isEntitySubscribedByMe});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -906,6 +917,7 @@ class __$$AuthorImplCopyWithImpl<$Res>
|
||||
Object? authorName = null,
|
||||
Object? authorProfileIcon = null,
|
||||
Object? authorIsMe = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
}) {
|
||||
return _then(_$AuthorImpl(
|
||||
authorSlug: null == authorSlug
|
||||
@@ -924,6 +936,10 @@ class __$$AuthorImplCopyWithImpl<$Res>
|
||||
? _value.authorIsMe
|
||||
: authorIsMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -935,7 +951,9 @@ class _$AuthorImpl implements _Author {
|
||||
{@JsonKey(name: "author_slug") required this.authorSlug,
|
||||
@JsonKey(name: "author_name") required this.authorName,
|
||||
@JsonKey(name: "author_profile_icon") required this.authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") required this.authorIsMe});
|
||||
@JsonKey(name: "author_is_me") required this.authorIsMe,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
required this.isEntitySubscribedByMe});
|
||||
|
||||
factory _$AuthorImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$AuthorImplFromJson(json);
|
||||
@@ -952,10 +970,13 @@ class _$AuthorImpl implements _Author {
|
||||
@override
|
||||
@JsonKey(name: "author_is_me")
|
||||
final bool authorIsMe;
|
||||
@override
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
final bool isEntitySubscribedByMe;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Author(authorSlug: $authorSlug, authorName: $authorName, authorProfileIcon: $authorProfileIcon, authorIsMe: $authorIsMe)';
|
||||
return 'Author(authorSlug: $authorSlug, authorName: $authorName, authorProfileIcon: $authorProfileIcon, authorIsMe: $authorIsMe, isEntitySubscribedByMe: $isEntitySubscribedByMe)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -970,13 +991,15 @@ class _$AuthorImpl implements _Author {
|
||||
(identical(other.authorProfileIcon, authorProfileIcon) ||
|
||||
other.authorProfileIcon == authorProfileIcon) &&
|
||||
(identical(other.authorIsMe, authorIsMe) ||
|
||||
other.authorIsMe == authorIsMe));
|
||||
other.authorIsMe == authorIsMe) &&
|
||||
(identical(other.isEntitySubscribedByMe, isEntitySubscribedByMe) ||
|
||||
other.isEntitySubscribedByMe == isEntitySubscribedByMe));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, authorSlug, authorName, authorProfileIcon, authorIsMe);
|
||||
int get hashCode => Object.hash(runtimeType, authorSlug, authorName,
|
||||
authorProfileIcon, authorIsMe, isEntitySubscribedByMe);
|
||||
|
||||
/// Create a copy of Author
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -996,12 +1019,13 @@ class _$AuthorImpl implements _Author {
|
||||
|
||||
abstract class _Author implements Author {
|
||||
const factory _Author(
|
||||
{@JsonKey(name: "author_slug") required final String authorSlug,
|
||||
@JsonKey(name: "author_name") required final String authorName,
|
||||
@JsonKey(name: "author_profile_icon")
|
||||
required final String authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") required final bool authorIsMe}) =
|
||||
_$AuthorImpl;
|
||||
{@JsonKey(name: "author_slug") required final String authorSlug,
|
||||
@JsonKey(name: "author_name") required final String authorName,
|
||||
@JsonKey(name: "author_profile_icon")
|
||||
required final String authorProfileIcon,
|
||||
@JsonKey(name: "author_is_me") required final bool authorIsMe,
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
required final bool isEntitySubscribedByMe}) = _$AuthorImpl;
|
||||
|
||||
factory _Author.fromJson(Map<String, dynamic> json) = _$AuthorImpl.fromJson;
|
||||
|
||||
@@ -1017,6 +1041,9 @@ abstract class _Author implements Author {
|
||||
@override
|
||||
@JsonKey(name: "author_is_me")
|
||||
bool get authorIsMe;
|
||||
@override
|
||||
@JsonKey(name: "is_entity_subscribed_by_me")
|
||||
bool get isEntitySubscribedByMe;
|
||||
|
||||
/// Create a copy of Author
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -74,6 +74,7 @@ _$AuthorImpl _$$AuthorImplFromJson(Map<String, dynamic> json) => _$AuthorImpl(
|
||||
authorName: json['author_name'] as String,
|
||||
authorProfileIcon: json['author_profile_icon'] as String,
|
||||
authorIsMe: json['author_is_me'] as bool,
|
||||
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
|
||||
@@ -82,6 +83,7 @@ Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
|
||||
'author_name': instance.authorName,
|
||||
'author_profile_icon': instance.authorProfileIcon,
|
||||
'author_is_me': instance.authorIsMe,
|
||||
'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe,
|
||||
};
|
||||
|
||||
_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -9,7 +10,24 @@ class FeedCubit extends Cubit<FeedState> {
|
||||
bool isLoading = false;
|
||||
String? _query;
|
||||
|
||||
FeedCubit({required this.feedRepository}) : super(const FeedState.loading());
|
||||
FeedCubit({
|
||||
required this.feedRepository,
|
||||
}) : super(const FeedState.loading());
|
||||
|
||||
updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
|
||||
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
||||
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
|
||||
feedBlogPostList[index] = feedBlogPostModel.copyWith(
|
||||
wasLikedByMe: wasLikedByMe,
|
||||
totalLikes: wasLikedByMe == false ? feedBlogPostList[index].totalLikes - 1 : feedBlogPostList[index].totalLikes + 1,
|
||||
);
|
||||
emit(
|
||||
FeedState.loaded(
|
||||
feedBlogPostList: feedBlogPostList,
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
loadFeedBlogPostList({
|
||||
String? query,
|
||||
|
||||
@@ -34,7 +34,7 @@ class FeedScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _FeedScreenState extends State<FeedScreen> {
|
||||
final FeedCubit feedCubit = sl<FeedCubit>()..loadFeedBlogPostList();
|
||||
final FeedCubit feedCubit = sl<FeedCubit>();
|
||||
final RecipesCubit recipesCubit = sl<RecipesCubit>()..loadRecipesList();
|
||||
final DietsCubit dietsCubit = sl<DietsCubit>()..loadDiets();
|
||||
final PlansCubit plansCubit = sl<PlansCubit>()..loadPlans();
|
||||
@@ -49,6 +49,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
feedCubit.loadFeedBlogPostList();
|
||||
blogPostsScrollController.addListener(_onScrollBlogPosts);
|
||||
recipesScrollController.addListener(_onScrollRecipes);
|
||||
dietsScrollController.addListener(_onScrollDiets);
|
||||
@@ -185,7 +186,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
if (index == state.feedBlogPostList.length) {
|
||||
return const Loader();
|
||||
}
|
||||
return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]);
|
||||
return BlogPostWidget(index: index);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
@@ -445,7 +446,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
GoRouter.of(context).pushNamed(CreatePlanScreen.name);
|
||||
break;
|
||||
default:
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedCubit);
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen');
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(64),
|
||||
@@ -470,6 +471,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
width: MediaQuery.of(context).size.width - 84,
|
||||
child: TextFormField(
|
||||
controller: searchController,
|
||||
autofocus: false,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
|
||||
@@ -4,21 +4,24 @@ import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class BlogPostWidget extends StatefulWidget {
|
||||
final FeedBlogPostModel feedBlogPostModel;
|
||||
final int index;
|
||||
|
||||
const BlogPostWidget({super.key, required this.feedBlogPostModel});
|
||||
const BlogPostWidget({
|
||||
super.key,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
State<BlogPostWidget> createState() => _BlogPostWidgetState();
|
||||
@@ -28,224 +31,209 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
late bool isLiked;
|
||||
late int totalLikes;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isLiked = widget.feedBlogPostModel.wasLikedByMe;
|
||||
totalLikes = widget.feedBlogPostModel.totalLikes;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: widget.feedBlogPostModel,
|
||||
),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
return BlocBuilder<FeedCubit, FeedState>(
|
||||
builder: (BuildContext context, FeedState state) {
|
||||
if (state is FeedStateLoaded) {
|
||||
isLiked = state.feedBlogPostList[widget.index].wasLikedByMe;
|
||||
totalLikes = state.feedBlogPostList[widget.index].totalLikes;
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: state.feedBlogPostList[widget.index],
|
||||
),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.black12,
|
||||
foregroundImage: NetworkImage(
|
||||
widget.feedBlogPostModel.author.authorProfileIcon,
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.black12,
|
||||
foregroundImage: NetworkImage(
|
||||
state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.feedBlogPostList[widget.index].author.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (!state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe && !state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||
SubscribeButtonWidget(
|
||||
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.feedBlogPostModel.author.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty)
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
state.feedBlogPostList[widget.index].title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
calculateDifference(
|
||||
widget.feedBlogPostModel.createdAt,
|
||||
),
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Fluttertoast.showToast(msg: 'ща доделаем');
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Ink(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: appTheme.gray200,
|
||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty && state.feedBlogPostList[widget.index].description.isNotEmpty) SizedBox(height: 8),
|
||||
if (state.feedBlogPostList[widget.index].description.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Html(
|
||||
data: state.feedBlogPostList[widget.index].description.length > 200
|
||||
? state.feedBlogPostList[widget.index].description.substring(0, 200)
|
||||
: state.feedBlogPostList[widget.index].description,
|
||||
style: {
|
||||
"*": Style(
|
||||
color: Colors.black87,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.feedBlogPostList[widget.index].description.length > 200)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Подписаться',
|
||||
'Читать полностью...',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1,
|
||||
color: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
widget.feedBlogPostModel.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.feedBlogPostModel.title.isNotEmpty && widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8),
|
||||
if (widget.feedBlogPostModel.description.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Html(
|
||||
data: widget.feedBlogPostModel.description.length > 200
|
||||
? widget.feedBlogPostModel.description.substring(0, 200)
|
||||
: widget.feedBlogPostModel.description,
|
||||
style: {
|
||||
"*": Style(
|
||||
color: Colors.black87,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.feedBlogPostModel.description.length > 200)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Читать полностью...',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
rootNavigator: true,
|
||||
PhotoViewScreen(
|
||||
imageUrls: [baseUrl + '/storage/' + widget.feedBlogPostModel.image],
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: baseUrl + '/storage/' + widget.feedBlogPostModel.image,
|
||||
child: CachedNetworkImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
imageUrl: baseUrl + '/storage/' + widget.feedBlogPostModel.image,
|
||||
errorWidget: (context, str, object) => SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
FeedIconButton(
|
||||
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
|
||||
text: totalLikes.toString(),
|
||||
iconColor: isLiked ? Colors.red.withOpacity(0.8) : null,
|
||||
backgroundColor: isLiked ? Colors.red.withOpacity(0.1) : null,
|
||||
textColor: isLiked ? Colors.red.withOpacity(0.8) : null,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
isLiked = !isLiked;
|
||||
isLiked ? totalLikes++ : totalLikes--;
|
||||
});
|
||||
isLiked
|
||||
? await context.read<FeedCubit>().likePost(widget.feedBlogPostModel.id)
|
||||
: await context.read<FeedCubit>().unlikedPost(widget.feedBlogPostModel.id);
|
||||
await context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty) SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
rootNavigator: true,
|
||||
PhotoViewScreen(
|
||||
imageUrls: [baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image],
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset(
|
||||
'assets/icons/comment.svg',
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.gray600,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
text: widget.feedBlogPostModel.totalComments.toString(),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: widget.feedBlogPostModel,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset(
|
||||
height: 20,
|
||||
width: 20,
|
||||
'assets/icons/share.svg',
|
||||
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
|
||||
),
|
||||
onTap: () => Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/blog_post/' + widget.feedBlogPostModel.id.toString(),
|
||||
child: Hero(
|
||||
tag: baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image,
|
||||
child: CachedNetworkImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
imageUrl: baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image,
|
||||
errorWidget: (context, str, object) => SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.visibility_rounded,
|
||||
size: 16,
|
||||
color: appTheme.gray500,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
widget.feedBlogPostModel.totalViews.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.gray500,
|
||||
fontWeight: FontWeight.w400,
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
FeedIconButton(
|
||||
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
|
||||
text: totalLikes.toString(),
|
||||
iconColor: isLiked ? Colors.red.withOpacity(0.8) : null,
|
||||
backgroundColor: isLiked ? Colors.red.withOpacity(0.1) : null,
|
||||
textColor: isLiked ? Colors.red.withOpacity(0.8) : null,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
isLiked = !isLiked;
|
||||
isLiked ? totalLikes++ : totalLikes--;
|
||||
});
|
||||
isLiked
|
||||
? await context.read<FeedCubit>().likePost(state.feedBlogPostList[widget.index].id)
|
||||
: await context.read<FeedCubit>().unlikedPost(state.feedBlogPostList[widget.index].id);
|
||||
await context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
},
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset(
|
||||
'assets/icons/comment.svg',
|
||||
height: 20,
|
||||
width: 20,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.gray600,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
text: state.feedBlogPostList[widget.index].totalComments.toString(),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'blogPostScreen',
|
||||
extra: state.feedBlogPostList[widget.index],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
FeedIconButton(
|
||||
svgIcon: SvgPicture.asset(
|
||||
height: 20,
|
||||
width: 20,
|
||||
'assets/icons/share.svg',
|
||||
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
|
||||
),
|
||||
onTap: () => Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/blog_post/' + state.feedBlogPostList[widget.index].id.toString(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.visibility_rounded,
|
||||
size: 16,
|
||||
color: appTheme.gray500,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
state.feedBlogPostList[widget.index].totalViews.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: appTheme.gray500,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,13 +77,16 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
comment.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: appTheme.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
child: Text(
|
||||
comment.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: appTheme.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubscribeButtonWidget extends StatefulWidget {
|
||||
final String slug;
|
||||
|
||||
const SubscribeButtonWidget({
|
||||
super.key,
|
||||
required this.slug,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SubscribeButtonWidget> createState() => _SubscribeButtonWidgetState();
|
||||
}
|
||||
|
||||
class _SubscribeButtonWidgetState extends State<SubscribeButtonWidget> {
|
||||
bool isSubscribe = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedOpacity(
|
||||
opacity: isSubscribe ? 0 : 1,
|
||||
duration: Duration(milliseconds: 250),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
setState(() {
|
||||
isSubscribe = true;
|
||||
});
|
||||
await sl<SubCubit>().subscribe(widget.slug);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
isSubscribe = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Ink(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: appTheme.gray200,
|
||||
),
|
||||
child: Text(
|
||||
'Подписаться',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,34 @@ class ProfileModel with _$ProfileModel {
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required String slug,
|
||||
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
||||
@JsonKey(name: "role") required String role,
|
||||
@JsonKey(name: "badges") required List<BadgeModel> badges,
|
||||
@JsonKey(name: "subscriptions") required List<ProfilePreviewModel> subscriptions,
|
||||
@JsonKey(name: "subscribers") required List<ProfilePreviewModel> subscribers,
|
||||
@JsonKey(name: "education_resource") EducationResource? educationResource,
|
||||
}) = _ProfileModel;
|
||||
|
||||
factory ProfileModel.fromJson(Map<String, dynamic> json) => _$ProfileModelFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class EducationResource with _$EducationResource {
|
||||
const factory EducationResource({
|
||||
@JsonKey(name: "has_medical_education") int? hasMedical_Education,
|
||||
@JsonKey(name: "vk_link") String? vkLink,
|
||||
@JsonKey(name: "insta_link") String? instaLink,
|
||||
@JsonKey(name: "fb_link") String? fbLink,
|
||||
@JsonKey(name: "tg_link") String? tgLink,
|
||||
@JsonKey(name: "personal_site_link") String? personalSiteLink,
|
||||
@JsonKey(name: "request_state") String? requestState,
|
||||
}) = _EducationResource;
|
||||
|
||||
factory EducationResource.fromJson(Map<String, dynamic> json) => _$EducationResourceFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfileDetailsModel with _$ProfileDetailsModel {
|
||||
const factory ProfileDetailsModel({
|
||||
|
||||
@@ -34,6 +34,8 @@ mixin _$ProfileModel {
|
||||
DateTime? get updatedAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_public_profile")
|
||||
bool get isPublicProfile => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "slug")
|
||||
String get slug => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "profile_photo_url")
|
||||
@@ -48,6 +50,9 @@ mixin _$ProfileModel {
|
||||
@JsonKey(name: "subscribers")
|
||||
List<ProfilePreviewModel> get subscribers =>
|
||||
throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "education_resource")
|
||||
EducationResource? get educationResource =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ProfileModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -73,12 +78,17 @@ abstract class $ProfileModelCopyWith<$Res> {
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") String slug,
|
||||
@JsonKey(name: "profile_photo_url") String profilePhotoUrl,
|
||||
@JsonKey(name: "role") String role,
|
||||
@JsonKey(name: "badges") List<BadgeModel> badges,
|
||||
@JsonKey(name: "subscriptions") List<ProfilePreviewModel> subscriptions,
|
||||
@JsonKey(name: "subscribers") List<ProfilePreviewModel> subscribers});
|
||||
@JsonKey(name: "subscribers") List<ProfilePreviewModel> subscribers,
|
||||
@JsonKey(name: "education_resource")
|
||||
EducationResource? educationResource});
|
||||
|
||||
$EducationResourceCopyWith<$Res>? get educationResource;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -103,12 +113,14 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel>
|
||||
Object? createdAt = freezed,
|
||||
Object? updatedAt = freezed,
|
||||
Object? isPublicProfile = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? slug = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
Object? badges = null,
|
||||
Object? subscriptions = null,
|
||||
Object? subscribers = null,
|
||||
Object? educationResource = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -139,6 +151,10 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel>
|
||||
? _value.isPublicProfile
|
||||
: isPublicProfile // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
slug: null == slug
|
||||
? _value.slug
|
||||
: slug // ignore: cast_nullable_to_non_nullable
|
||||
@@ -163,8 +179,26 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel>
|
||||
? _value.subscribers
|
||||
: subscribers // ignore: cast_nullable_to_non_nullable
|
||||
as List<ProfilePreviewModel>,
|
||||
educationResource: freezed == educationResource
|
||||
? _value.educationResource
|
||||
: educationResource // ignore: cast_nullable_to_non_nullable
|
||||
as EducationResource?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of ProfileModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$EducationResourceCopyWith<$Res>? get educationResource {
|
||||
if (_value.educationResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $EducationResourceCopyWith<$Res>(_value.educationResource!, (value) {
|
||||
return _then(_value.copyWith(educationResource: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -183,12 +217,18 @@ abstract class _$$ProfileModelImplCopyWith<$Res>
|
||||
@JsonKey(name: "created_at") DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") String slug,
|
||||
@JsonKey(name: "profile_photo_url") String profilePhotoUrl,
|
||||
@JsonKey(name: "role") String role,
|
||||
@JsonKey(name: "badges") List<BadgeModel> badges,
|
||||
@JsonKey(name: "subscriptions") List<ProfilePreviewModel> subscriptions,
|
||||
@JsonKey(name: "subscribers") List<ProfilePreviewModel> subscribers});
|
||||
@JsonKey(name: "subscribers") List<ProfilePreviewModel> subscribers,
|
||||
@JsonKey(name: "education_resource")
|
||||
EducationResource? educationResource});
|
||||
|
||||
@override
|
||||
$EducationResourceCopyWith<$Res>? get educationResource;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -211,12 +251,14 @@ class __$$ProfileModelImplCopyWithImpl<$Res>
|
||||
Object? createdAt = freezed,
|
||||
Object? updatedAt = freezed,
|
||||
Object? isPublicProfile = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? slug = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
Object? badges = null,
|
||||
Object? subscriptions = null,
|
||||
Object? subscribers = null,
|
||||
Object? educationResource = freezed,
|
||||
}) {
|
||||
return _then(_$ProfileModelImpl(
|
||||
id: null == id
|
||||
@@ -247,6 +289,10 @@ class __$$ProfileModelImplCopyWithImpl<$Res>
|
||||
? _value.isPublicProfile
|
||||
: isPublicProfile // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
slug: null == slug
|
||||
? _value.slug
|
||||
: slug // ignore: cast_nullable_to_non_nullable
|
||||
@@ -271,6 +317,10 @@ class __$$ProfileModelImplCopyWithImpl<$Res>
|
||||
? _value._subscribers
|
||||
: subscribers // ignore: cast_nullable_to_non_nullable
|
||||
as List<ProfilePreviewModel>,
|
||||
educationResource: freezed == educationResource
|
||||
? _value.educationResource
|
||||
: educationResource // ignore: cast_nullable_to_non_nullable
|
||||
as EducationResource?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -286,6 +336,8 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
@JsonKey(name: "created_at") this.createdAt,
|
||||
@JsonKey(name: "updated_at") this.updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required this.isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required this.isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required this.slug,
|
||||
@JsonKey(name: "profile_photo_url") required this.profilePhotoUrl,
|
||||
@JsonKey(name: "role") required this.role,
|
||||
@@ -293,7 +345,8 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
@JsonKey(name: "subscriptions")
|
||||
required final List<ProfilePreviewModel> subscriptions,
|
||||
@JsonKey(name: "subscribers")
|
||||
required final List<ProfilePreviewModel> subscribers})
|
||||
required final List<ProfilePreviewModel> subscribers,
|
||||
@JsonKey(name: "education_resource") this.educationResource})
|
||||
: _badges = badges,
|
||||
_subscriptions = subscriptions,
|
||||
_subscribers = subscribers;
|
||||
@@ -323,6 +376,9 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
@JsonKey(name: "is_public_profile")
|
||||
final bool isPublicProfile;
|
||||
@override
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
final bool isEntitySubscribedByMe;
|
||||
@override
|
||||
@JsonKey(name: "slug")
|
||||
final String slug;
|
||||
@override
|
||||
@@ -358,9 +414,13 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
return EqualUnmodifiableListView(_subscribers);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(name: "education_resource")
|
||||
final EducationResource? educationResource;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfileModel(id: $id, name: $name, email: $email, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, slug: $slug, profilePhotoUrl: $profilePhotoUrl, role: $role, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers)';
|
||||
return 'ProfileModel(id: $id, name: $name, email: $email, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, isEntitySubscribedByMe: $isEntitySubscribedByMe, slug: $slug, profilePhotoUrl: $profilePhotoUrl, role: $role, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers, educationResource: $educationResource)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -379,6 +439,8 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.isPublicProfile, isPublicProfile) ||
|
||||
other.isPublicProfile == isPublicProfile) &&
|
||||
(identical(other.isEntitySubscribedByMe, isEntitySubscribedByMe) ||
|
||||
other.isEntitySubscribedByMe == isEntitySubscribedByMe) &&
|
||||
(identical(other.slug, slug) || other.slug == slug) &&
|
||||
(identical(other.profilePhotoUrl, profilePhotoUrl) ||
|
||||
other.profilePhotoUrl == profilePhotoUrl) &&
|
||||
@@ -387,7 +449,9 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._subscriptions, _subscriptions) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._subscribers, _subscribers));
|
||||
.equals(other._subscribers, _subscribers) &&
|
||||
(identical(other.educationResource, educationResource) ||
|
||||
other.educationResource == educationResource));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -401,12 +465,14 @@ class _$ProfileModelImpl implements _ProfileModel {
|
||||
createdAt,
|
||||
updatedAt,
|
||||
isPublicProfile,
|
||||
isEntitySubscribedByMe,
|
||||
slug,
|
||||
profilePhotoUrl,
|
||||
role,
|
||||
const DeepCollectionEquality().hash(_badges),
|
||||
const DeepCollectionEquality().hash(_subscriptions),
|
||||
const DeepCollectionEquality().hash(_subscribers));
|
||||
const DeepCollectionEquality().hash(_subscribers),
|
||||
educationResource);
|
||||
|
||||
/// Create a copy of ProfileModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -433,6 +499,8 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@JsonKey(name: "created_at") final DateTime? createdAt,
|
||||
@JsonKey(name: "updated_at") final DateTime? updatedAt,
|
||||
@JsonKey(name: "is_public_profile") required final bool isPublicProfile,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required final bool isEntitySubscribedByMe,
|
||||
@JsonKey(name: "slug") required final String slug,
|
||||
@JsonKey(name: "profile_photo_url") required final String profilePhotoUrl,
|
||||
@JsonKey(name: "role") required final String role,
|
||||
@@ -440,8 +508,9 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@JsonKey(name: "subscriptions")
|
||||
required final List<ProfilePreviewModel> subscriptions,
|
||||
@JsonKey(name: "subscribers")
|
||||
required final List<ProfilePreviewModel>
|
||||
subscribers}) = _$ProfileModelImpl;
|
||||
required final List<ProfilePreviewModel> subscribers,
|
||||
@JsonKey(name: "education_resource")
|
||||
final EducationResource? educationResource}) = _$ProfileModelImpl;
|
||||
|
||||
factory _ProfileModel.fromJson(Map<String, dynamic> json) =
|
||||
_$ProfileModelImpl.fromJson;
|
||||
@@ -468,6 +537,9 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@JsonKey(name: "is_public_profile")
|
||||
bool get isPublicProfile;
|
||||
@override
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe;
|
||||
@override
|
||||
@JsonKey(name: "slug")
|
||||
String get slug;
|
||||
@override
|
||||
@@ -485,6 +557,9 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
@override
|
||||
@JsonKey(name: "subscribers")
|
||||
List<ProfilePreviewModel> get subscribers;
|
||||
@override
|
||||
@JsonKey(name: "education_resource")
|
||||
EducationResource? get educationResource;
|
||||
|
||||
/// Create a copy of ProfileModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -494,6 +569,307 @@ abstract class _ProfileModel implements ProfileModel {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
EducationResource _$EducationResourceFromJson(Map<String, dynamic> json) {
|
||||
return _EducationResource.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$EducationResource {
|
||||
@JsonKey(name: "has_medical_education")
|
||||
int? get hasMedical_Education => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "vk_link")
|
||||
String? get vkLink => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "insta_link")
|
||||
String? get instaLink => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "fb_link")
|
||||
String? get fbLink => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "tg_link")
|
||||
String? get tgLink => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "personal_site_link")
|
||||
String? get personalSiteLink => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "request_state")
|
||||
String? get requestState => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this EducationResource to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of EducationResource
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$EducationResourceCopyWith<EducationResource> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $EducationResourceCopyWith<$Res> {
|
||||
factory $EducationResourceCopyWith(
|
||||
EducationResource value, $Res Function(EducationResource) then) =
|
||||
_$EducationResourceCopyWithImpl<$Res, EducationResource>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "has_medical_education") int? hasMedical_Education,
|
||||
@JsonKey(name: "vk_link") String? vkLink,
|
||||
@JsonKey(name: "insta_link") String? instaLink,
|
||||
@JsonKey(name: "fb_link") String? fbLink,
|
||||
@JsonKey(name: "tg_link") String? tgLink,
|
||||
@JsonKey(name: "personal_site_link") String? personalSiteLink,
|
||||
@JsonKey(name: "request_state") String? requestState});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$EducationResourceCopyWithImpl<$Res, $Val extends EducationResource>
|
||||
implements $EducationResourceCopyWith<$Res> {
|
||||
_$EducationResourceCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of EducationResource
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? hasMedical_Education = freezed,
|
||||
Object? vkLink = freezed,
|
||||
Object? instaLink = freezed,
|
||||
Object? fbLink = freezed,
|
||||
Object? tgLink = freezed,
|
||||
Object? personalSiteLink = freezed,
|
||||
Object? requestState = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
hasMedical_Education: freezed == hasMedical_Education
|
||||
? _value.hasMedical_Education
|
||||
: hasMedical_Education // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
vkLink: freezed == vkLink
|
||||
? _value.vkLink
|
||||
: vkLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
instaLink: freezed == instaLink
|
||||
? _value.instaLink
|
||||
: instaLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
fbLink: freezed == fbLink
|
||||
? _value.fbLink
|
||||
: fbLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tgLink: freezed == tgLink
|
||||
? _value.tgLink
|
||||
: tgLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
personalSiteLink: freezed == personalSiteLink
|
||||
? _value.personalSiteLink
|
||||
: personalSiteLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
requestState: freezed == requestState
|
||||
? _value.requestState
|
||||
: requestState // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$EducationResourceImplCopyWith<$Res>
|
||||
implements $EducationResourceCopyWith<$Res> {
|
||||
factory _$$EducationResourceImplCopyWith(_$EducationResourceImpl value,
|
||||
$Res Function(_$EducationResourceImpl) then) =
|
||||
__$$EducationResourceImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "has_medical_education") int? hasMedical_Education,
|
||||
@JsonKey(name: "vk_link") String? vkLink,
|
||||
@JsonKey(name: "insta_link") String? instaLink,
|
||||
@JsonKey(name: "fb_link") String? fbLink,
|
||||
@JsonKey(name: "tg_link") String? tgLink,
|
||||
@JsonKey(name: "personal_site_link") String? personalSiteLink,
|
||||
@JsonKey(name: "request_state") String? requestState});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$EducationResourceImplCopyWithImpl<$Res>
|
||||
extends _$EducationResourceCopyWithImpl<$Res, _$EducationResourceImpl>
|
||||
implements _$$EducationResourceImplCopyWith<$Res> {
|
||||
__$$EducationResourceImplCopyWithImpl(_$EducationResourceImpl _value,
|
||||
$Res Function(_$EducationResourceImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of EducationResource
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? hasMedical_Education = freezed,
|
||||
Object? vkLink = freezed,
|
||||
Object? instaLink = freezed,
|
||||
Object? fbLink = freezed,
|
||||
Object? tgLink = freezed,
|
||||
Object? personalSiteLink = freezed,
|
||||
Object? requestState = freezed,
|
||||
}) {
|
||||
return _then(_$EducationResourceImpl(
|
||||
hasMedical_Education: freezed == hasMedical_Education
|
||||
? _value.hasMedical_Education
|
||||
: hasMedical_Education // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
vkLink: freezed == vkLink
|
||||
? _value.vkLink
|
||||
: vkLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
instaLink: freezed == instaLink
|
||||
? _value.instaLink
|
||||
: instaLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
fbLink: freezed == fbLink
|
||||
? _value.fbLink
|
||||
: fbLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tgLink: freezed == tgLink
|
||||
? _value.tgLink
|
||||
: tgLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
personalSiteLink: freezed == personalSiteLink
|
||||
? _value.personalSiteLink
|
||||
: personalSiteLink // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
requestState: freezed == requestState
|
||||
? _value.requestState
|
||||
: requestState // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$EducationResourceImpl implements _EducationResource {
|
||||
const _$EducationResourceImpl(
|
||||
{@JsonKey(name: "has_medical_education") this.hasMedical_Education,
|
||||
@JsonKey(name: "vk_link") this.vkLink,
|
||||
@JsonKey(name: "insta_link") this.instaLink,
|
||||
@JsonKey(name: "fb_link") this.fbLink,
|
||||
@JsonKey(name: "tg_link") this.tgLink,
|
||||
@JsonKey(name: "personal_site_link") this.personalSiteLink,
|
||||
@JsonKey(name: "request_state") this.requestState});
|
||||
|
||||
factory _$EducationResourceImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$EducationResourceImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: "has_medical_education")
|
||||
final int? hasMedical_Education;
|
||||
@override
|
||||
@JsonKey(name: "vk_link")
|
||||
final String? vkLink;
|
||||
@override
|
||||
@JsonKey(name: "insta_link")
|
||||
final String? instaLink;
|
||||
@override
|
||||
@JsonKey(name: "fb_link")
|
||||
final String? fbLink;
|
||||
@override
|
||||
@JsonKey(name: "tg_link")
|
||||
final String? tgLink;
|
||||
@override
|
||||
@JsonKey(name: "personal_site_link")
|
||||
final String? personalSiteLink;
|
||||
@override
|
||||
@JsonKey(name: "request_state")
|
||||
final String? requestState;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'EducationResource(hasMedical_Education: $hasMedical_Education, vkLink: $vkLink, instaLink: $instaLink, fbLink: $fbLink, tgLink: $tgLink, personalSiteLink: $personalSiteLink, requestState: $requestState)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$EducationResourceImpl &&
|
||||
(identical(other.hasMedical_Education, hasMedical_Education) ||
|
||||
other.hasMedical_Education == hasMedical_Education) &&
|
||||
(identical(other.vkLink, vkLink) || other.vkLink == vkLink) &&
|
||||
(identical(other.instaLink, instaLink) ||
|
||||
other.instaLink == instaLink) &&
|
||||
(identical(other.fbLink, fbLink) || other.fbLink == fbLink) &&
|
||||
(identical(other.tgLink, tgLink) || other.tgLink == tgLink) &&
|
||||
(identical(other.personalSiteLink, personalSiteLink) ||
|
||||
other.personalSiteLink == personalSiteLink) &&
|
||||
(identical(other.requestState, requestState) ||
|
||||
other.requestState == requestState));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, hasMedical_Education, vkLink,
|
||||
instaLink, fbLink, tgLink, personalSiteLink, requestState);
|
||||
|
||||
/// Create a copy of EducationResource
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$EducationResourceImplCopyWith<_$EducationResourceImpl> get copyWith =>
|
||||
__$$EducationResourceImplCopyWithImpl<_$EducationResourceImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$EducationResourceImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _EducationResource implements EducationResource {
|
||||
const factory _EducationResource(
|
||||
{@JsonKey(name: "has_medical_education") final int? hasMedical_Education,
|
||||
@JsonKey(name: "vk_link") final String? vkLink,
|
||||
@JsonKey(name: "insta_link") final String? instaLink,
|
||||
@JsonKey(name: "fb_link") final String? fbLink,
|
||||
@JsonKey(name: "tg_link") final String? tgLink,
|
||||
@JsonKey(name: "personal_site_link") final String? personalSiteLink,
|
||||
@JsonKey(name: "request_state")
|
||||
final String? requestState}) = _$EducationResourceImpl;
|
||||
|
||||
factory _EducationResource.fromJson(Map<String, dynamic> json) =
|
||||
_$EducationResourceImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: "has_medical_education")
|
||||
int? get hasMedical_Education;
|
||||
@override
|
||||
@JsonKey(name: "vk_link")
|
||||
String? get vkLink;
|
||||
@override
|
||||
@JsonKey(name: "insta_link")
|
||||
String? get instaLink;
|
||||
@override
|
||||
@JsonKey(name: "fb_link")
|
||||
String? get fbLink;
|
||||
@override
|
||||
@JsonKey(name: "tg_link")
|
||||
String? get tgLink;
|
||||
@override
|
||||
@JsonKey(name: "personal_site_link")
|
||||
String? get personalSiteLink;
|
||||
@override
|
||||
@JsonKey(name: "request_state")
|
||||
String? get requestState;
|
||||
|
||||
/// Create a copy of EducationResource
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$EducationResourceImplCopyWith<_$EducationResourceImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ProfileDetailsModel _$ProfileDetailsModelFromJson(Map<String, dynamic> json) {
|
||||
return _ProfileDetailsModel.fromJson(json);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ _$ProfileModelImpl _$$ProfileModelImplFromJson(Map<String, dynamic> json) =>
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
isPublicProfile: json['is_public_profile'] as bool,
|
||||
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool,
|
||||
slug: json['slug'] as String,
|
||||
profilePhotoUrl: json['profile_photo_url'] as String,
|
||||
role: json['role'] as String,
|
||||
@@ -33,6 +34,10 @@ _$ProfileModelImpl _$$ProfileModelImplFromJson(Map<String, dynamic> json) =>
|
||||
subscribers: (json['subscribers'] as List<dynamic>)
|
||||
.map((e) => ProfilePreviewModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
educationResource: json['education_resource'] == null
|
||||
? null
|
||||
: EducationResource.fromJson(
|
||||
json['education_resource'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfileModelImplToJson(_$ProfileModelImpl instance) =>
|
||||
@@ -44,12 +49,38 @@ Map<String, dynamic> _$$ProfileModelImplToJson(_$ProfileModelImpl instance) =>
|
||||
'created_at': instance.createdAt?.toIso8601String(),
|
||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||
'is_public_profile': instance.isPublicProfile,
|
||||
'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe,
|
||||
'slug': instance.slug,
|
||||
'profile_photo_url': instance.profilePhotoUrl,
|
||||
'role': instance.role,
|
||||
'badges': instance.badges,
|
||||
'subscriptions': instance.subscriptions,
|
||||
'subscribers': instance.subscribers,
|
||||
'education_resource': instance.educationResource,
|
||||
};
|
||||
|
||||
_$EducationResourceImpl _$$EducationResourceImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$EducationResourceImpl(
|
||||
hasMedical_Education: (json['has_medical_education'] as num?)?.toInt(),
|
||||
vkLink: json['vk_link'] as String?,
|
||||
instaLink: json['insta_link'] as String?,
|
||||
fbLink: json['fb_link'] as String?,
|
||||
tgLink: json['tg_link'] as String?,
|
||||
personalSiteLink: json['personal_site_link'] as String?,
|
||||
requestState: json['request_state'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$EducationResourceImplToJson(
|
||||
_$EducationResourceImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'has_medical_education': instance.hasMedical_Education,
|
||||
'vk_link': instance.vkLink,
|
||||
'insta_link': instance.instaLink,
|
||||
'fb_link': instance.fbLink,
|
||||
'tg_link': instance.tgLink,
|
||||
'personal_site_link': instance.personalSiteLink,
|
||||
'request_state': instance.requestState,
|
||||
};
|
||||
|
||||
_$ProfileDetailsModelImpl _$$ProfileDetailsModelImplFromJson(
|
||||
|
||||
@@ -24,8 +24,7 @@ class SubModel with _$SubModel {
|
||||
required String name,
|
||||
required String city,
|
||||
required DateTime birthdate,
|
||||
@JsonKey(name: 'is_subscribe')
|
||||
required bool isSubscribe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||
required String email,
|
||||
@JsonKey(name: 'profile_photo_url') required String profilePhotoUrl,
|
||||
required String role,
|
||||
|
||||
@@ -249,8 +249,8 @@ mixin _$SubModel {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get city => throw _privateConstructorUsedError;
|
||||
DateTime get birthdate => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_subscribe')
|
||||
bool get isSubscribe => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe => throw _privateConstructorUsedError;
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'profile_photo_url')
|
||||
String get profilePhotoUrl => throw _privateConstructorUsedError;
|
||||
@@ -283,7 +283,7 @@ abstract class $SubModelCopyWith<$Res> {
|
||||
String name,
|
||||
String city,
|
||||
DateTime birthdate,
|
||||
@JsonKey(name: 'is_subscribe') bool isSubscribe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
String email,
|
||||
@JsonKey(name: 'profile_photo_url') String profilePhotoUrl,
|
||||
String role,
|
||||
@@ -312,7 +312,7 @@ class _$SubModelCopyWithImpl<$Res, $Val extends SubModel>
|
||||
Object? name = null,
|
||||
Object? city = null,
|
||||
Object? birthdate = null,
|
||||
Object? isSubscribe = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? email = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
@@ -341,9 +341,9 @@ class _$SubModelCopyWithImpl<$Res, $Val extends SubModel>
|
||||
? _value.birthdate
|
||||
: birthdate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
isSubscribe: null == isSubscribe
|
||||
? _value.isSubscribe
|
||||
: isSubscribe // ignore: cast_nullable_to_non_nullable
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
email: null == email
|
||||
? _value.email
|
||||
@@ -387,7 +387,7 @@ abstract class _$$SubModelImplCopyWith<$Res>
|
||||
String name,
|
||||
String city,
|
||||
DateTime birthdate,
|
||||
@JsonKey(name: 'is_subscribe') bool isSubscribe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe,
|
||||
String email,
|
||||
@JsonKey(name: 'profile_photo_url') String profilePhotoUrl,
|
||||
String role,
|
||||
@@ -414,7 +414,7 @@ class __$$SubModelImplCopyWithImpl<$Res>
|
||||
Object? name = null,
|
||||
Object? city = null,
|
||||
Object? birthdate = null,
|
||||
Object? isSubscribe = null,
|
||||
Object? isEntitySubscribedByMe = null,
|
||||
Object? email = null,
|
||||
Object? profilePhotoUrl = null,
|
||||
Object? role = null,
|
||||
@@ -443,9 +443,9 @@ class __$$SubModelImplCopyWithImpl<$Res>
|
||||
? _value.birthdate
|
||||
: birthdate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
isSubscribe: null == isSubscribe
|
||||
? _value.isSubscribe
|
||||
: isSubscribe // ignore: cast_nullable_to_non_nullable
|
||||
isEntitySubscribedByMe: null == isEntitySubscribedByMe
|
||||
? _value.isEntitySubscribedByMe
|
||||
: isEntitySubscribedByMe // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
email: null == email
|
||||
? _value.email
|
||||
@@ -484,7 +484,8 @@ class _$SubModelImpl implements _SubModel {
|
||||
required this.name,
|
||||
required this.city,
|
||||
required this.birthdate,
|
||||
@JsonKey(name: 'is_subscribe') required this.isSubscribe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required this.isEntitySubscribedByMe,
|
||||
required this.email,
|
||||
@JsonKey(name: 'profile_photo_url') required this.profilePhotoUrl,
|
||||
required this.role,
|
||||
@@ -506,8 +507,8 @@ class _$SubModelImpl implements _SubModel {
|
||||
@override
|
||||
final DateTime birthdate;
|
||||
@override
|
||||
@JsonKey(name: 'is_subscribe')
|
||||
final bool isSubscribe;
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
final bool isEntitySubscribedByMe;
|
||||
@override
|
||||
final String email;
|
||||
@override
|
||||
@@ -527,7 +528,7 @@ class _$SubModelImpl implements _SubModel {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SubModel(id: $id, slug: $slug, name: $name, city: $city, birthdate: $birthdate, isSubscribe: $isSubscribe, email: $email, profilePhotoUrl: $profilePhotoUrl, role: $role, isPublicProfile: $isPublicProfile, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||
return 'SubModel(id: $id, slug: $slug, name: $name, city: $city, birthdate: $birthdate, isEntitySubscribedByMe: $isEntitySubscribedByMe, email: $email, profilePhotoUrl: $profilePhotoUrl, role: $role, isPublicProfile: $isPublicProfile, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -541,8 +542,8 @@ class _$SubModelImpl implements _SubModel {
|
||||
(identical(other.city, city) || other.city == city) &&
|
||||
(identical(other.birthdate, birthdate) ||
|
||||
other.birthdate == birthdate) &&
|
||||
(identical(other.isSubscribe, isSubscribe) ||
|
||||
other.isSubscribe == isSubscribe) &&
|
||||
(identical(other.isEntitySubscribedByMe, isEntitySubscribedByMe) ||
|
||||
other.isEntitySubscribedByMe == isEntitySubscribedByMe) &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.profilePhotoUrl, profilePhotoUrl) ||
|
||||
other.profilePhotoUrl == profilePhotoUrl) &&
|
||||
@@ -564,7 +565,7 @@ class _$SubModelImpl implements _SubModel {
|
||||
name,
|
||||
city,
|
||||
birthdate,
|
||||
isSubscribe,
|
||||
isEntitySubscribedByMe,
|
||||
email,
|
||||
profilePhotoUrl,
|
||||
role,
|
||||
@@ -595,7 +596,8 @@ abstract class _SubModel implements SubModel {
|
||||
required final String name,
|
||||
required final String city,
|
||||
required final DateTime birthdate,
|
||||
@JsonKey(name: 'is_subscribe') required final bool isSubscribe,
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
required final bool isEntitySubscribedByMe,
|
||||
required final String email,
|
||||
@JsonKey(name: 'profile_photo_url') required final String profilePhotoUrl,
|
||||
required final String role,
|
||||
@@ -618,8 +620,8 @@ abstract class _SubModel implements SubModel {
|
||||
@override
|
||||
DateTime get birthdate;
|
||||
@override
|
||||
@JsonKey(name: 'is_subscribe')
|
||||
bool get isSubscribe;
|
||||
@JsonKey(name: 'is_entity_subscribed_by_me')
|
||||
bool get isEntitySubscribedByMe;
|
||||
@override
|
||||
String get email;
|
||||
@override
|
||||
|
||||
@@ -31,7 +31,7 @@ _$SubModelImpl _$$SubModelImplFromJson(Map<String, dynamic> json) =>
|
||||
name: json['name'] as String,
|
||||
city: json['city'] as String,
|
||||
birthdate: DateTime.parse(json['birthdate'] as String),
|
||||
isSubscribe: json['is_subscribe'] as bool,
|
||||
isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool,
|
||||
email: json['email'] as String,
|
||||
profilePhotoUrl: json['profile_photo_url'] as String,
|
||||
role: json['role'] as String,
|
||||
@@ -47,7 +47,7 @@ Map<String, dynamic> _$$SubModelImplToJson(_$SubModelImpl instance) =>
|
||||
'name': instance.name,
|
||||
'city': instance.city,
|
||||
'birthdate': instance.birthdate.toIso8601String(),
|
||||
'is_subscribe': instance.isSubscribe,
|
||||
'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe,
|
||||
'email': instance.email,
|
||||
'profile_photo_url': instance.profilePhotoUrl,
|
||||
'role': instance.role,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
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/profile/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
@@ -15,11 +18,13 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
|
||||
getProfileBySlug(String slug) async {
|
||||
try {
|
||||
emit(ProfileState.loaded(
|
||||
profile: await profileRepository.getProfileBySlug(slug),
|
||||
profileDetails: await profileRepository.getMyProfileDetails(),
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug),
|
||||
),);
|
||||
emit(
|
||||
ProfileState.loaded(
|
||||
profile: await profileRepository.getProfileBySlug(slug),
|
||||
profileDetails: await profileRepository.getProfileDetailsBySlug(slug),
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ProfileState.error(errorMessage: e.toString()));
|
||||
}
|
||||
@@ -31,13 +36,15 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
_currentPeriod = period;
|
||||
try {
|
||||
final profile = await profileRepository.getMyProfile();
|
||||
emit(ProfileState.loaded(
|
||||
profile: profile,
|
||||
profileDetails: await profileRepository.getMyProfileDetails(),
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug),
|
||||
weightPoints: await profileRepository.getWeight(period: period),
|
||||
circumferencePoints: await profileRepository.getCircumferences(period: period),
|
||||
),);
|
||||
emit(
|
||||
ProfileState.loaded(
|
||||
profile: profile,
|
||||
profileDetails: await profileRepository.getMyProfileDetails(),
|
||||
blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: profile.slug),
|
||||
weightPoints: await profileRepository.getWeight(period: period),
|
||||
circumferencePoints: await profileRepository.getCircumferences(period: period),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ProfileState.error(errorMessage: e.toString()));
|
||||
}
|
||||
@@ -80,4 +87,14 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
uploadPhoto(File file) async {
|
||||
await profileRepository.uploadPhoto(await ImageCompressUtility.compressFile(file));
|
||||
return await getMyProfile();
|
||||
}
|
||||
|
||||
deletePhoto() async {
|
||||
await profileRepository.deletePhoto();
|
||||
return await getMyProfile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
final Author? author;
|
||||
@@ -40,6 +51,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
List<CircumferenceModel> circumferencePoints = [];
|
||||
int _selectedChipIndex = 0;
|
||||
int _selectedIntervalChipIndex = 0;
|
||||
late bool canSub;
|
||||
bool isFirstFetch = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -57,6 +70,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
circumferencePoints = state.circumferencePoints!;
|
||||
weightPoints = state.weightPoints!;
|
||||
}
|
||||
if (isFirstFetch) {
|
||||
canSub = !state.profile.isEntitySubscribedByMe;
|
||||
isFirstFetch = false;
|
||||
}
|
||||
return widget.author == null ? _myProfileScreen(state) : _profileScreen(state);
|
||||
} else if (state is ProfileStateError) {
|
||||
return Scaffold(
|
||||
@@ -99,17 +116,17 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
_header(state),
|
||||
if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state),
|
||||
const SizedBox(height: 8),
|
||||
_messageAndSubscribe(),
|
||||
_messageAndSubscribe(state),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
_subscriptionsAndSubscribers(state),
|
||||
if (state.profile.badges.isNotEmpty) _badges(state),
|
||||
_socialLinks(state),
|
||||
_education(state),
|
||||
_diplomasAndCertificates(state),
|
||||
_activePlans(state),
|
||||
if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _socialLinks(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _education(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _diplomasAndCertificates(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _activePlans(state)),
|
||||
_blogPosts(state),
|
||||
],
|
||||
),
|
||||
@@ -444,11 +461,38 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
_socialLinkButton(() {}, 'assets/icons/vk.svg'),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.vkLink ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
'assets/icons/vk.svg',
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(() {}, 'assets/icons/telegram.svg'),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.tgLink ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
'assets/icons/telegram.svg',
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(() {}, 'assets/icons/web.svg'),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.personalSiteLink ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
'assets/icons/web.svg',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -662,7 +706,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => BlogPostWidget(
|
||||
feedBlogPostModel: state.blogPosts[index],
|
||||
index: index,
|
||||
),
|
||||
separatorBuilder: (context, index) => SizedBox(height: 8),
|
||||
itemCount: state.blogPosts.length,
|
||||
@@ -692,16 +736,100 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 32,
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
PopupMenuButton(
|
||||
onSelected: (value) async {
|
||||
if (value == 'view') {
|
||||
context.pushTransparentRoute(
|
||||
rootNavigator: true,
|
||||
PhotoViewScreen(
|
||||
imageUrls: [state.profile.profilePhotoUrl],
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (value == 'replace') {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
actions: [
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final picker = ImagePicker();
|
||||
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||
if (image != null) {
|
||||
await profileCubit.uploadPhoto(File(image.path));
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo_camera),
|
||||
SizedBox(width: 8),
|
||||
Text('Фото'),
|
||||
],
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final picker = ImagePicker();
|
||||
XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
||||
if (image != null) {
|
||||
await profileCubit.uploadPhoto(File(image.path));
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo),
|
||||
SizedBox(width: 8),
|
||||
Text('Галерея'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
isDestructiveAction: true,
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (value == 'delete') {
|
||||
await profileCubit.deletePhoto();
|
||||
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'view',
|
||||
child: Text('Посмотреть'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'replace',
|
||||
child: Text('Заменить'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
],
|
||||
child: Hero(
|
||||
tag: state.profile.profilePhotoUrl,
|
||||
child: CircleAvatar(
|
||||
radius: 32,
|
||||
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.profile.name,
|
||||
state.profileDetails.firstName + ' ' + state.profileDetails.lastName,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -710,37 +838,110 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 20,
|
||||
color: Color(0xFF6D6F71),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Дополнительно',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF6D6F71),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
showDragHandle: true,
|
||||
useSafeArea: true,
|
||||
context: context,
|
||||
builder: (context) => Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Подробнее',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_additional('Дата рожденя: ', '${state.profileDetails.birthdate}'),
|
||||
const SizedBox(height: 12),
|
||||
_additional('Страна: ', '${state.profileDetails.country}'),
|
||||
const SizedBox(height: 12),
|
||||
_additional('Город: ', '${state.profileDetails.city}'),
|
||||
const SizedBox(height: 12),
|
||||
_additional('Язык общения: ', '${state.profileDetails.lang}'),
|
||||
const SizedBox(height: 12),
|
||||
_additional('В Eatvio с: ', '${state.profile.createdAt?.format('d.MM.yyyy', 'ru')}'),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 20,
|
||||
color: Color(0xFF6D6F71),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Дополнительно',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF6D6F71),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.more_horiz_rounded),
|
||||
PopupMenuButton(
|
||||
onSelected: (value) {
|
||||
Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/profile/' + state.profile.slug,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'share',
|
||||
child: Text('Поделиться'),
|
||||
),
|
||||
],
|
||||
child: Icon(Icons.more_horiz_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_messageAndSubscribe() {
|
||||
_additional(String title, String value) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF6D6F71),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_messageAndSubscribe(ProfileStateLoaded state) {
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
@@ -768,18 +969,26 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8),
|
||||
width: (MediaQuery.of(context).size.width - 40) / 2,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
'Подписаться',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
canSub = !canSub;
|
||||
});
|
||||
canSub ? sl<SubCubit>().unsubscribe(state.profile.slug) : sl<SubCubit>().subscribe(state.profile.slug);
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8),
|
||||
width: (MediaQuery.of(context).size.width - 40) / 2,
|
||||
decoration: BoxDecoration(
|
||||
color: !canSub ? Colors.grey.shade200 : appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
!canSub ? 'Отписаться' : 'Подписаться',
|
||||
style: TextStyle(
|
||||
color: !canSub ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -826,7 +1035,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
return ImtChartWidget(
|
||||
weightPoints: weightPoints,
|
||||
);
|
||||
default: return SizedBox.shrink();
|
||||
default:
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -27,6 +27,7 @@ class _SubWidgetState extends State<SubWidget> {
|
||||
authorName: widget.subModel.name,
|
||||
authorProfileIcon: widget.subModel.profilePhotoUrl,
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
),
|
||||
),
|
||||
child: Ink(
|
||||
@@ -65,7 +66,7 @@ class _SubWidgetState extends State<SubWidget> {
|
||||
),
|
||||
const Spacer(),
|
||||
SubActionButtonWidget(
|
||||
subActionType: widget.subModel.isSubscribe ? SubActionType.SendMessage : SubActionType.Subscribe,
|
||||
subActionType: widget.subModel.isEntitySubscribedByMe ? SubActionType.SendMessage : SubActionType.Subscribe,
|
||||
slug: widget.subModel.slug,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
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';
|
||||
@@ -20,6 +22,14 @@ class ProfileRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ProfileDetailsModel> getProfileDetailsBySlug(String slug) async {
|
||||
try {
|
||||
return await profileService.getProfileDetailsBySlug(slug: slug);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ProfileModel> getMyProfile() async {
|
||||
try {
|
||||
return await profileService.getMyProfile();
|
||||
@@ -145,4 +155,20 @@ class ProfileRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadPhoto(File file) async {
|
||||
try {
|
||||
return await profileService.uploadPhoto(photo: file);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deletePhoto() async {
|
||||
try {
|
||||
return await profileService.deletePhoto();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
@@ -5,6 +7,7 @@ import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/sub_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'profile_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
@@ -22,6 +25,11 @@ abstract class ProfileService {
|
||||
@GET('/api/profile/detailz')
|
||||
Future<ProfileDetailsModel> getMyProfileDetails();
|
||||
|
||||
@GET('/api/profile/detailz/{slug}')
|
||||
Future<ProfileDetailsModel> getProfileDetailsBySlug({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
@POST('/api/profile/addCircumference')
|
||||
Future<void> addCircumference({
|
||||
@Part(name: 'chest_circumference') required int chestCircumference,
|
||||
@@ -82,4 +90,13 @@ abstract class ProfileService {
|
||||
Future<void> unsubscribe({
|
||||
@Path() required String slug,
|
||||
});
|
||||
|
||||
@POST('/api/profile/photo/upload')
|
||||
@MultiPart()
|
||||
Future<void> uploadPhoto({
|
||||
@Part(name: 'photo') required File photo,
|
||||
});
|
||||
|
||||
@DELETE('/api/profile/photo/delete')
|
||||
Future<void> deletePhoto();
|
||||
}
|
||||
|
||||
@@ -120,6 +120,40 @@ class _ProfileService implements ProfileService {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ProfileDetailsModel> getProfileDetailsBySlug(
|
||||
{required String slug}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<ProfileDetailsModel>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/detailz/${slug}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late ProfileDetailsModel _value;
|
||||
try {
|
||||
_value = ProfileDetailsModel.fromJson(_result.data!);
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addCircumference({
|
||||
required int chestCircumference,
|
||||
@@ -504,6 +538,64 @@ class _ProfileService implements ProfileService {
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> uploadPhoto({required File photo}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = FormData();
|
||||
_data.files.add(MapEntry(
|
||||
'photo',
|
||||
MultipartFile.fromFileSync(
|
||||
photo.path,
|
||||
filename: photo.path.split(Platform.pathSeparator).last,
|
||||
),
|
||||
));
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/photo/upload',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePhoto() async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<void>(Options(
|
||||
method: 'DELETE',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/photo/delete',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -26,9 +28,11 @@ class MyApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<InitializationCubit>(create: (context) => sl<InitializationCubit>()),
|
||||
BlocProvider<InitializationCubit>(create: (context) => sl<InitializationCubit>()..runInitialization()),
|
||||
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
|
||||
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
|
||||
],
|
||||
child: Portal(
|
||||
child: KeyboardDismisser(
|
||||
|
||||
@@ -48,7 +48,7 @@ dependencies:
|
||||
file_picker: ^8.1.4
|
||||
audioplayers: ^6.1.0
|
||||
intl: ^0.19.0
|
||||
rive: ^0.13.18
|
||||
rive: ^0.13.19
|
||||
mask_text_input_formatter: ^2.9.0
|
||||
lottie: ^3.1.3
|
||||
flutter_portal: ^1.1.4
|
||||
@@ -58,6 +58,7 @@ dependencies:
|
||||
image: ^4.3.0
|
||||
flutter_native_splash: ^2.4.3
|
||||
dotted_border: ^2.1.0
|
||||
http: ^1.2.2
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
|
||||
Reference in New Issue
Block a user