Merge pull request 'main' (#19) from eatvio/eatvio_app:main into main

Reviewed-on: BLoC/eatvio_app#19
This commit is contained in:
BLoC
2025-03-05 10:49:45 +00:00
14 changed files with 180 additions and 112 deletions

View File

@@ -364,11 +364,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -389,7 +387,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Eatvio distribution profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -516,11 +513,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -541,7 +536,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Eatvio distribution profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -556,11 +550,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = VL68C36HYS;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -581,7 +573,6 @@
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Eatvio distribution profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
@@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="70" y="-2"/>
</scene>
</scenes>
</document>

View File

@@ -1,10 +1,5 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
@@ -65,30 +60,12 @@ class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static GoRouter appRouter = GoRouter(
initialLocation: '/auth-screen',
initialLocation: '/my-profile-screen',
navigatorKey: navigatorKey,
routes: [
GoRoute(
path: '/auth-screen',
name: 'authScreen',
redirect: (context, state) async {
// TokenStorage.removeTokenInStorage();
// await TokenStorage.saveTokenInStorage('Bearer 3|laravel_sanctum_zoubsO9NPXAqgLZEdHV9R3XbuDUHJJMJyXcd77mhbb649f04');
final token = await TokenStorage.getTokenFromStorage();
if (token != null) {
try {
await sl<WsService>().getWebsocket();
return '/my-profile-screen';
} on DioException catch (e, st) {
if (e.response?.statusCode == 401 && e.response?.statusMessage == 'Unauthenticated.') {
TokenStorage.removeTokenInStorage();
}
log(e.toString(), stackTrace: st);
}
}
return null;
},
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
routes: [
GoRoute(

View File

@@ -0,0 +1,31 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:go_router/go_router.dart';
class TokenInterceptor extends Interceptor {
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
final token = await TokenStorage.getTokenFromStorage();
if (token != null && token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
super.onRequest(options, handler);
}
@override
Future<void> onError(
DioException err,
ErrorInterceptorHandler handler,
) async {
if (err.response?.statusCode == 401) {
await TokenStorage.removeTokenInStorage();
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
}
super.onError(err, handler);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.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.dart';
@@ -166,13 +167,13 @@ Future<void> init() async {
sl.registerLazySingleton(
() => Dio(
BaseOptions(
baseUrl: baseUrl,
baseUrl: baseUrl,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
},
),
),
)..interceptors.add(TokenInterceptor()),
);
sl.registerLazySingleton(() async => await FlutterSecureStorage());
}

View File

@@ -1,18 +1,12 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
abstract class TokenStorage {
static _addTokenToDioHeader(String token) {
sl<Dio>().options.headers['Authorization'] = 'Bearer $token';
}
static Future<bool> haveTokenInStorage() async {
try {
if (await FlutterSecureStorage().read(key: 'token') == null) {
return false;
} else {
_addTokenToDioHeader((await FlutterSecureStorage().read(key: 'token'))!);
} else {
return true;
}
} catch (e) {
@@ -23,9 +17,8 @@ abstract class TokenStorage {
static Future<String?> getTokenFromStorage() async => await FlutterSecureStorage().read(key: 'token');
static saveTokenInStorage(String token) async {
await FlutterSecureStorage().write(key: 'token', value: token);
_addTokenToDioHeader(token);
await FlutterSecureStorage().write(key: 'token', value: token);
}
static removeTokenInStorage() async => (await FlutterSecureStorage().delete(key: 'token'));
static Future<void> removeTokenInStorage() async => (await FlutterSecureStorage().delete(key: 'token'));
}

View File

@@ -319,9 +319,11 @@ class _ViolenceAndHostilityState extends State<_ViolenceAndHostility> {
});
},
),
Text(
element.$2,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
Expanded(
child: Text(
element.$2,
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
),
),
],
);

View File

@@ -43,8 +43,11 @@ class ComplainingCubit extends Cubit<ComplainingState> {
if (category != null && contentType != null) {
switch (contentType) {
case ComplainedContent.diet:
emit(state.copyWith(wasSentSuccessfully: true));
return;
if (id != null) {
await repository.sendDietComplain(id: id, category: category.getValue(), body: state.complainInfo);
emit(state.copyWith(wasSentSuccessfully: true));
return;
}
case ComplainedContent.plan:
if (slug != null) {
await repository.sendPlanComplain(slug: slug, category: category.getValue(), body: state.complainInfo);

View File

@@ -11,7 +11,7 @@ class ComplainingRepository {
required ComplainModel body,
}) async {
try {
service.sendPlanComplain(slug: slug, category: category, body: body);
await service.sendPlanComplain(slug: slug, category: category, body: body);
} catch (e) {
rethrow;
}
@@ -23,7 +23,7 @@ class ComplainingRepository {
required ComplainModel body,
}) async {
try {
service.sendRecipeComplain(slug: slug, category: category, body: body);
await service.sendRecipeComplain(slug: slug, category: category, body: body);
} catch (e) {
rethrow;
}
@@ -35,7 +35,7 @@ class ComplainingRepository {
required ComplainModel body,
}) async {
try {
service.sendBlogPostsComplain(id: id, category: category, body: body);
await service.sendBlogPostsComplain(id: id, category: category, body: body);
} catch (e) {
rethrow;
}
@@ -47,7 +47,19 @@ class ComplainingRepository {
required ComplainModel body,
}) async {
try {
service.sendProfileComplain(slug: slug, category: category, body: body);
await service.sendProfileComplain(slug: slug, category: category, body: body);
} catch (e) {
rethrow;
}
}
Future<void> sendDietComplain({
required int id,
required String category,
required ComplainModel body,
}) async {
try {
await service.sendDietComplain(id: id, category: category, body: body);
} catch (e) {
rethrow;
}

View File

@@ -35,5 +35,12 @@ abstract class ComplainingService {
@Path() required String category,
@Body() required ComplainModel body,
});
@POST('/api/diets/{id}/complain/{category}')
Future<void> sendDietComplain({
@Path() required int id,
@Path() required String category,
@Body() required ComplainModel body,
});
}

View File

@@ -109,6 +109,29 @@ class _ComplainingService implements ComplainingService {
await _dio.fetch<void>(_options);
}
@override
Future<void> sendDietComplain({
required int id,
required String category,
required ComplainModel body,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = body;
final _options = _setStreamType<void>(
Options(method: 'POST', headers: _headers, extra: _extra)
.compose(
_dio.options,
'/api/diets/${id}/complain/${category}',
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 ||

View File

@@ -90,6 +90,7 @@ class MoreOptions extends StatelessWidget {
if (complainedContentType != null) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ComplainingBottomSheet(
complainedContentType: complainedContentType!,
id: id,

View File

@@ -6,49 +6,57 @@ part of 'diets_pagination_model.dart';
// JsonSerializableGenerator
// **************************************************************************
_$DietsPaginationImpl _$$DietsPaginationImplFromJson(Map<String, dynamic> json) => _$DietsPaginationImpl(
data: (json['data'] as List<dynamic>).map((e) => Diet.fromJson(e as Map<String, dynamic>)).toList(),
_$DietsPaginationImpl _$$DietsPaginationImplFromJson(
Map<String, dynamic> json) =>
_$DietsPaginationImpl(
data: (json['data'] as List<dynamic>)
.map((e) => Diet.fromJson(e as Map<String, dynamic>))
.toList(),
links: Links.fromJson(json['links'] as Map<String, dynamic>),
meta: Meta.fromJson(json['meta'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$DietsPaginationImplToJson(_$DietsPaginationImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$DietsPaginationImplToJson(
_$DietsPaginationImpl instance) =>
<String, dynamic>{
'data': instance.data,
'links': instance.links,
'meta': instance.meta,
};
_$DietImpl _$$DietImplFromJson(Map<String, dynamic> json) {
return _$DietImpl(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
image: json['image'] as String,
duration: (json['duration'] as num).toInt(),
state: $enumDecode(_$DietStateEnumMap, json['state']),
type: DietType.fromJson(json['type'] as Map<String, dynamic>),
details: json['details'] as String,
dailySchedule: (json['daily_schedule'] as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(int.parse(k), DailySchedule.fromJson(e as Map<String, dynamic>)),
)),
)),
),
author: Author.fromJson(json['author'] as Map<String, dynamic>),
totalLikes: (json['total_likes'] as num).toInt(),
totalViews: (json['total_views'] as num).toInt(),
totalComments: (json['total_comments'] as num).toInt(),
wasLikedByMe: json['was_liked_by_me'] as bool,
isFavorite: json['is_favorite'] as bool,
dailyNutrition: (json['daily_nutrition'] as List<dynamic>).map((e) => DailyNutrition.fromJson(e as Map<String, dynamic>)).toList(),
);
}
_$DietImpl _$$DietImplFromJson(Map<String, dynamic> json) => _$DietImpl(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
image: json['image'] as String,
duration: (json['duration'] as num).toInt(),
state: $enumDecode(_$DietStateEnumMap, json['state']),
type: DietType.fromJson(json['type'] as Map<String, dynamic>),
details: json['details'] as String,
dailySchedule: (json['daily_schedule'] as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(
int.parse(k),
(e as Map<String, dynamic>).map(
(k, e) => MapEntry(int.parse(k),
DailySchedule.fromJson(e as Map<String, dynamic>)),
)),
)),
),
author: Author.fromJson(json['author'] as Map<String, dynamic>),
totalLikes: (json['total_likes'] as num).toInt(),
totalViews: (json['total_views'] as num).toInt(),
totalComments: (json['total_comments'] as num).toInt(),
wasLikedByMe: json['was_liked_by_me'] as bool,
isFavorite: json['is_favorite'] as bool,
dailyNutrition: (json['daily_nutrition'] as List<dynamic>)
.map((e) => DailyNutrition.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'image': instance.image,
@@ -56,8 +64,10 @@ Map<String, dynamic> _$$DietImplToJson(_$DietImpl instance) => <String, dynamic>
'state': _$DietStateEnumMap[instance.state]!,
'type': instance.type,
'details': instance.details,
'daily_schedule':
instance.dailySchedule.map((k, e) => MapEntry(k.toString(), e.map((k, e) => MapEntry(k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))),
'daily_schedule': instance.dailySchedule.map((k, e) => MapEntry(
k.toString(),
e.map((k, e) => MapEntry(
k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))),
'author': instance.author,
'total_likes': instance.totalLikes,
'total_views': instance.totalViews,
@@ -73,11 +83,13 @@ const _$DietStateEnumMap = {
DietState.personal: 'personal',
};
_$CreatedDietImpl _$$CreatedDietImplFromJson(Map<String, dynamic> json) => _$CreatedDietImpl(
_$CreatedDietImpl _$$CreatedDietImplFromJson(Map<String, dynamic> json) =>
_$CreatedDietImpl(
id: (json['id'] as num).toInt(),
);
Map<String, dynamic> _$$CreatedDietImplToJson(_$CreatedDietImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$CreatedDietImplToJson(_$CreatedDietImpl instance) =>
<String, dynamic>{
'id': instance.id,
};
@@ -90,7 +102,8 @@ _$AuthorImpl _$$AuthorImplFromJson(Map<String, dynamic> json) => _$AuthorImpl(
authorPlaceInRating: (json['author_place_in_rating'] as num?)?.toInt(),
);
Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) =>
<String, dynamic>{
'author_slug': instance.authorSlug,
'author_name': instance.authorName,
'author_profile_icon': instance.authorProfileIcon,
@@ -99,18 +112,23 @@ Map<String, dynamic> _$$AuthorImplToJson(_$AuthorImpl instance) => <String, dyna
'author_place_in_rating': instance.authorPlaceInRating,
};
_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) => _$DailyScheduleImpl(
_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map<String, dynamic> json) =>
_$DailyScheduleImpl(
name: json['name'] as String,
portion: $enumDecode(_$PortionEnumMap, json['portion']),
quantity: _parseQuantity(json['quantity']),
dailyNutrition: json['nutrition'] == null ? null : DailyNutrition.fromJson(json['nutrition'] as Map<String, dynamic>),
dailyNutrition: json['nutrition'] == null
? null
: DailyNutrition.fromJson(json['nutrition'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) =>
<String, dynamic>{
'name': instance.name,
'portion': _$PortionEnumMap[instance.portion]!,
'quantity': instance.quantity,
if (instance.productNutritionalValueId case final value?) 'product_nutritional_value_id': value,
if (instance.productNutritionalValueId case final value?)
'product_nutritional_value_id': value,
};
const _$PortionEnumMap = {
@@ -119,28 +137,33 @@ const _$PortionEnumMap = {
Portion.PCS: 'pcs',
};
_$DietTypeImpl _$$DietTypeImplFromJson(Map<String, dynamic> json) => _$DietTypeImpl(
_$DietTypeImpl _$$DietTypeImplFromJson(Map<String, dynamic> json) =>
_$DietTypeImpl(
light: json['light'] as bool?,
veg: json['veg'] as bool?,
carb: json['carb'] as bool?,
meat: json['meat'] as bool?,
);
Map<String, dynamic> _$$DietTypeImplToJson(_$DietTypeImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$DietTypeImplToJson(_$DietTypeImpl instance) =>
<String, dynamic>{
'light': instance.light,
'veg': instance.veg,
'carb': instance.carb,
'meat': instance.meat,
};
_$DailyNutritionImpl _$$DailyNutritionImplFromJson(Map<String, dynamic> json) => _$DailyNutritionImpl(
_$DailyNutritionImpl _$$DailyNutritionImplFromJson(Map<String, dynamic> json) =>
_$DailyNutritionImpl(
energy: (json['energy'] as num).toDouble(),
proteins: (json['proteins'] as num).toDouble(),
fats: (json['fats'] as num).toDouble(),
carbohydrates: (json['carbohydrates'] as num).toDouble(),
);
Map<String, dynamic> _$$DailyNutritionImplToJson(_$DailyNutritionImpl instance) => <String, dynamic>{
Map<String, dynamic> _$$DailyNutritionImplToJson(
_$DailyNutritionImpl instance) =>
<String, dynamic>{
'energy': instance.energy,
'proteins': instance.proteins,
'fats': instance.fats,

View File

@@ -1021,6 +1021,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
} else {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ComplainingBottomSheet(
complainedContentType: ComplainedContent.profile,
slug: widget.author?.authorSlug,