Merge pull request 'feat(views)' (#697) from misha/eatvio_app:issue-687 into main

Reviewed-on: eatvio/eatvio_app#697
This commit is contained in:
2025-09-17 12:39:58 +00:00
18 changed files with 317 additions and 168 deletions

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@
*.mocks.dart
android/build/
ios/XCBuildData/
env/.env
env/.env
.env

View File

@@ -410,6 +410,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -560,6 +561,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -598,6 +600,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -5,6 +5,9 @@ import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
@@ -179,6 +182,7 @@ Future<void> init() async {
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
@@ -212,6 +216,7 @@ Future<void> init() async {
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
@@ -247,6 +252,7 @@ Future<void> init() async {
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
// External
sl.registerLazySingleton(

View File

@@ -0,0 +1,128 @@
import 'dart:async';
import 'package:eatvio_app/core/views/cubit/views_state.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ViewsCubit extends Cubit<ViewsCubitState> {
final ViewsRepository viewsRepository;
ViewsCubit({required this.viewsRepository}) : super(ViewsCubitState()) {
_listener = AppLifecycleListener(onPause: _sendViews, onInactive: _sendViews);
}
late final AppLifecycleListener _listener;
final Set<int> postsIds = <int>{};
final Set<int> plansIds = <int>{};
final Set<int> dietsIds = <int>{};
final Set<int> recipesIds = <int>{};
final Set<int> _sentPosts = <int>{};
final Set<int> _sentPlans = <int>{};
final Set<int> _sentDiets = <int>{};
final Set<int> _sentRecipes = <int>{};
final Map<ViewableContentType, Map<int, Timer>> _visibilityTimers = {
ViewableContentType.posts: <int, Timer>{},
ViewableContentType.plans: <int, Timer>{},
ViewableContentType.diets: <int, Timer>{},
ViewableContentType.recipes: <int, Timer>{},
};
Timer? _debounceTimer;
Set<int> _idsForType(ViewableContentType type) {
switch (type) {
case ViewableContentType.posts:
return postsIds;
case ViewableContentType.plans:
return plansIds;
case ViewableContentType.diets:
return dietsIds;
case ViewableContentType.recipes:
return recipesIds;
}
}
Set<int> _sentIdsForType(ViewableContentType type) {
switch (type) {
case ViewableContentType.posts:
return _sentPosts;
case ViewableContentType.plans:
return _sentPlans;
case ViewableContentType.diets:
return _sentDiets;
case ViewableContentType.recipes:
return _sentRecipes;
}
}
void handleVisibility({required double visibleFraction, required int id, required ViewableContentType type}) {
final timers = _visibilityTimers[type]!;
final ids = _idsForType(type);
final sent = _sentIdsForType(type);
if (sent.contains(id)) {
timers[id]?.cancel();
timers.remove(id);
return;
}
if (ids.contains(id)) return;
if (visibleFraction >= 0.5) {
if (timers.containsKey(id)) return;
timers[id] = Timer(const Duration(seconds: 1), () {
ids.add(id);
timers.remove(id);
_scheduleDebouncedSend();
});
} else {
timers[id]?.cancel();
timers.remove(id);
}
}
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
await viewsRepository.viewContent(
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
);
}
void _scheduleDebouncedSend() {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(seconds: 4), _sendViews);
}
void _send(Set<int> ids, Set<int> sent, ViewableContentType type) {
final newIds = ids.difference(sent);
if (newIds.isNotEmpty) {
_viewContent(type: type, ids: newIds.toList());
sent.addAll(newIds);
ids.removeAll(newIds);
}
}
void _sendViews() {
_send(postsIds, _sentPosts, ViewableContentType.posts);
_send(plansIds, _sentPlans, ViewableContentType.plans);
_send(dietsIds, _sentDiets, ViewableContentType.diets);
_send(recipesIds, _sentRecipes, ViewableContentType.recipes);
}
@override
Future<void> close() async {
for (final map in _visibilityTimers.values) {
for (final t in map.values) {
t.cancel();
}
map.clear();
}
_debounceTimer?.cancel();
_listener.dispose();
_sendViews();
await super.close();
}
}

View File

@@ -0,0 +1,3 @@
class ViewsCubitState {
ViewsCubitState();
}

View File

@@ -0,0 +1,16 @@
import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
class ViewsRepository {
final ViewsService viewsService;
ViewsRepository({required this.viewsService});
Future<void> viewContent({required ViewModel viewModel}) async {
try {
await viewsService.viewContent(viewModel: viewModel);
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,15 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:retrofit/retrofit.dart';
part 'views_service.g.dart';
@RestApi()
abstract class ViewsService {
factory ViewsService(Dio dio, {String baseUrl}) = _ViewsService;
@POST('/api/views')
Future<void> viewContent({
@Body() required ViewModel viewModel,
});
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'view_model.freezed.dart';
part 'view_model.g.dart';
@freezed
abstract class ViewModel with _$ViewModel {
factory ViewModel({
required String type,
List<int>? ids,
List<String>? slugs,
}) = _ViewModel;
factory ViewModel.fromJson(Map<String, dynamic> json) => _$ViewModelFromJson(json);
}

View File

@@ -81,4 +81,5 @@ class FeedCubit extends Cubit<FeedState> {
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
}

View File

@@ -1,5 +1,6 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/widgets/add_item_button.dart';
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
@@ -66,12 +67,9 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final dietsScrollController = ScrollController();
final plansScrollController = ScrollController();
final consultantsScrollController = ScrollController();
@override
void initState() {
// Initialize chipsLabelList based on hidePlans setting
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
consultantsScrollController.addListener(_onScrollConsultants);
super.initState();
}
@@ -80,6 +78,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
}
@override
void dispose() {
blogPostsScrollController.dispose();
@@ -90,6 +90,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
super.dispose();
}
@override
Widget build(BuildContext context) {
FlutterNativeSplash.remove();
@@ -228,10 +230,10 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
];
},
body: switch (_selectedChipIndex) {
0 => const BlogPosts(),
1 => const Recipes(),
2 => const Diets(),
3 when !hidePlans => const Plans(),
0 => BlogPosts(handleVisibility: context.read<ViewsCubit>().handleVisibility),
1 => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
2 => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
3 when hidePlans => const Consultants(),
4 when !hidePlans => const Consultants(),
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
@@ -240,3 +242,20 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
);
}
}
enum ViewableContentType {
plans,
posts,
diets,
recipes;
String getStringifiedVersion() => switch (this) {
ViewableContentType.plans => 'plans',
ViewableContentType.posts => 'blog_posts',
ViewableContentType.diets => 'diets',
ViewableContentType.recipes => 'recipes',
};
}

View File

@@ -3,13 +3,16 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:visibility_detector/visibility_detector.dart';
class BlogPosts extends StatelessWidget {
const BlogPosts({super.key});
const BlogPosts({super.key, required this.handleVisibility});
final Function({required double visibleFraction,required int id, required ViewableContentType type}) handleVisibility;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
@@ -29,25 +32,26 @@ class BlogPosts extends StatelessWidget {
if (index == state.feedBlogPostList.length) {
return const Loader();
}
return BlogPostWidget(index: index);
return VisibilityDetector(
onVisibilityChanged: (info) {
final id = state.feedBlogPostList[index].id;
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.posts);
},
key: ValueKey(index),
child: BlogPostWidget(index: index),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
),
);
} else if (state is FeedStateError) {
return DataErrorWidget(
onReload: () => context.read<FeedCubit>().loadFeedBlogPostList(),
);
return DataErrorWidget(onReload: () => context.read<FeedCubit>().loadFeedBlogPostList());
} else {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: const Loader(),
);
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
}
},
),
);
}
}

View File

@@ -3,15 +3,17 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:visibility_detector/visibility_detector.dart';
class Diets extends StatelessWidget {
const Diets({super.key});
const Diets({super.key, required this.handleVisibility});
final Function({required double visibleFraction, required int id, required ViewableContentType type}) handleVisibility;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
@@ -19,10 +21,7 @@ class Diets extends StatelessWidget {
child: BlocBuilder<DietsCubit, DietsState>(
builder: (BuildContext context, DietsState state) {
if (state.isLoading) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: const Loader(),
);
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
} else if (state.errorMessage != null) {
return DataErrorWidget(onReload: () => context.read<DietsCubit>().loadDiets());
} else {
@@ -39,7 +38,14 @@ class Diets extends StatelessWidget {
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return DietsWidget(diet: state.dietsList[index], index: index);
return VisibilityDetector(
key: ValueKey(index),
onVisibilityChanged: (info) {
final id = state.dietsList[index].id;
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.diets);
},
child: DietsWidget(diet: state.dietsList[index], index: index),
);
},
separatorBuilder: (context, index) {
return const SizedBox(height: 8);
@@ -50,11 +56,7 @@ class Diets extends StatelessWidget {
Positioned(
right: 0,
bottom: 74,
child: FilterBadge(
filterBottomSheet: DietFilterBottomSheet(
dietsCubit: context.read<DietsCubit>(),
),
),
child: FilterBadge(filterBottomSheet: DietFilterBottomSheet(dietsCubit: context.read<DietsCubit>())),
),
],
);

View File

@@ -3,15 +3,17 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/plans_filter_bottom_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:visibility_detector/visibility_detector.dart';
class Plans extends StatelessWidget {
const Plans({super.key});
const Plans({super.key, required this.handleVisibility});
final Function({required double visibleFraction, required int id, required ViewableContentType type}) handleVisibility;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
@@ -19,10 +21,7 @@ class Plans extends StatelessWidget {
child: BlocBuilder<PlansCubit, PlansState>(
builder: (BuildContext context, PlansState state) {
if (state.isLoading) {
return Scaffold(
backgroundColor: Colors.grey.shade200,
body: const Loader(),
);
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
} else if (state.errorMessage != null) {
return DataErrorWidget(onReload: () => context.read<PlansCubit>().loadPlans());
}
@@ -39,9 +38,13 @@ class Plans extends StatelessWidget {
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return PlanWidget(
plan: state.plans[index],
index: index,
return VisibilityDetector(
key: ValueKey(index),
onVisibilityChanged: (info) {
final id = state.plans[index].id;
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.plans);
},
child: PlanWidget(plan: state.plans[index], index: index),
);
},
separatorBuilder: (context, index) {
@@ -53,11 +56,7 @@ class Plans extends StatelessWidget {
Positioned(
right: 0,
bottom: 74,
child: FilterBadge(
filterBottomSheet: PlansFilterBottomSheet(
plansCubit: context.read<PlansCubit>(),
),
),
child: FilterBadge(filterBottomSheet: PlansFilterBottomSheet(plansCubit: context.read<PlansCubit>())),
),
],
);

View File

@@ -3,13 +3,15 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:visibility_detector/visibility_detector.dart';
class Recipes extends StatelessWidget {
const Recipes({super.key});
const Recipes({super.key, required this.handleVisibility});
final Function({required double visibleFraction,required int id, required ViewableContentType type}) handleVisibility;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
@@ -40,9 +42,16 @@ class Recipes extends StatelessWidget {
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return RecipesWidget(
recipe: state.recipesList[index],
index: index,
return VisibilityDetector(
key: ValueKey(index),
onVisibilityChanged: (info) {
final id = state.recipesList[index].id;
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.recipes);
},
child: RecipesWidget(
recipe: state.recipesList[index],
index: index,
),
);
},
separatorBuilder: (context, index) {

View File

@@ -5,103 +5,55 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.da
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
class FeedRepository {
final FeedService feedService;
FeedRepository({
required this.feedService,
});
FeedRepository({required this.feedService});
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({
String? query,
int page = 1,
}) async {
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
try {
return (await feedService.getBlogPostList(
page: page,
query: query,
));
return (await feedService.getBlogPostList(page: page, query: query));
} catch (e) {
rethrow;
}
}
Future<FeedBlogPostModel> getBlogPost({
required int id
}) async {
Future<FeedBlogPostModel> getBlogPost({required int id}) async {
try {
return await feedService.getBlogPost(
postId: id
);
return await feedService.getBlogPost(postId: id);
} catch (e) {
rethrow;
}
}
Future<List<Recipe>> getRecipes({
String? query,
List<int>? tags,
int page = 1,
}) async {
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
try {
return (await feedService.getRecipes(
query: query,
tags: tags,
page: page,
))
.recipesList;
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
} catch (e) {
rethrow;
}
}
Future<DietsPagination> getDiets({
int page = 1,
String? query,
String? types,
int? duration,
String? sorting,
}) async {
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
try {
return (await feedService.getDiets(
page: page,
query: query,
types: types,
duration: duration,
sorting: sorting,
));
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
} catch (e) {
rethrow;
}
}
Future<PlanPaginationModel> getPlans({
int page = 1,
String? query,
String? types,
int? duration,
int? minPrice,
int? maxPrice,
}) async {
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
try {
return (await feedService.getPlans(
page: page,
query: query,
types: types,
duration: duration,
minPrice: minPrice,
maxPrice: maxPrice,
));
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
} catch (e) {
rethrow;
}
}
Future<CommentModel> getBlogPostComments({
required String id,
}) async {
Future<CommentModel> getBlogPostComments({required String id}) async {
try {
return (await feedService.getBlogPostComments(id: id));
} catch (e) {
@@ -109,25 +61,15 @@ class FeedRepository {
}
}
Future<void> sendBlogPostComment({
required String id,
required String commentText,
int? parentCommentId,
}) async {
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendBlogPostComment(
id: id,
body: commentText,
parentCommentId: parentCommentId,
);
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
rethrow;
}
}
Future<CommentModel> getDietsComments({
required String id,
}) async {
Future<CommentModel> getDietsComments({required String id}) async {
try {
return (await feedService.getDietsComments(id: id));
} catch (e) {
@@ -135,25 +77,15 @@ class FeedRepository {
}
}
Future<void> sendDietsComment({
required String id,
required String commentText,
int? parentCommentId,
}) async {
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendDietsComment(
id: id,
body: commentText,
parentCommentId: parentCommentId,
);
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
rethrow;
}
}
Future<CommentModel> getRecipesComments({
required String id,
}) async {
Future<CommentModel> getRecipesComments({required String id}) async {
try {
return (await feedService.getRecipesComments(id: id));
} catch (e) {
@@ -161,25 +93,15 @@ class FeedRepository {
}
}
Future<void> sendRecipesComment({
required String id,
required String commentText,
int? parentCommentId,
}) async {
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendRecipesComment(
id: id,
body: commentText,
parentCommentId: parentCommentId,
);
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
rethrow;
}
}
Future<CommentModel> getPlansComments({
required String slug,
}) async {
Future<CommentModel> getPlansComments({required String slug}) async {
try {
return (await feedService.getPlansComments(slug: slug));
} catch (e) {
@@ -187,25 +109,15 @@ class FeedRepository {
}
}
Future<void> sendPlansComment({
required String slug,
required String commentText,
int? parentCommentId,
}) async {
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
try {
await feedService.sendPlansComment(
slug: slug,
body: commentText,
parentCommentId: parentCommentId,
);
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
} catch (e) {
rethrow;
}
}
Future<void> likeBlogPost({
required int id,
}) async {
Future<void> likeBlogPost({required int id}) async {
try {
await feedService.likeBlogPost(id: id);
} catch (e) {
@@ -213,9 +125,7 @@ class FeedRepository {
}
}
Future<void> unlikedBlogPost({
required int id,
}) async {
Future<void> unlikedBlogPost({required int id}) async {
try {
await feedService.unlikedBlogPost(id: id);
} catch (e) {
@@ -262,4 +172,12 @@ class FeedRepository {
rethrow;
}
}
Future<void> viewContent({required ViewModel viewModel}) async {
try {
await feedService.viewContent(viewModel: viewModel);
} catch (e) {
rethrow;
}
}
}

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.da
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:retrofit/retrofit.dart';
part 'feed_service.g.dart';
@@ -131,4 +132,9 @@ abstract class FeedService {
Future<void> viewPlan({
@Path() required String slug,
});
@POST('/api/views')
Future<void> viewContent({
@Body() required ViewModel viewModel,
});
}

View File

@@ -1,4 +1,3 @@
import 'dart:developer';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/locator_service.dart' as di;
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
@@ -67,7 +68,10 @@ class MyApp extends StatelessWidget {
child: MaterialApp.router(
theme: ThemeHelper().themeData(),
routerConfig: AppRouter.appRouter,
builder: (context, child) => InternetConnectionCheckerScreen(child: child!),
builder: (context, child) => BlocProvider(
create: (context) => sl<ViewsCubit>(),
child: InternetConnectionCheckerScreen(child: child!),
),
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
S.delegate,