Merge remote-tracking branch 'upstream/main' into issue-720

This commit is contained in:
misha
2025-10-10 14:50:37 +03:00
68 changed files with 1055 additions and 259 deletions

View File

@@ -0,0 +1,37 @@
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/main.dart' as app;
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../common_cases/skip_welcome_screen.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
await AppInitializer.initialize(buildType: AppBuildType.tests);
});
testWidgets('Signs in successfully with valid credentials', (WidgetTester tester) async {
await tester.pumpWidget(const app.MyApp());
await tester.pumpAndSettle(const Duration(seconds: 3));
await skipWelcomeScreen(tester);
final emailFieldFinder = find.byKey(AppKeys.emailTextFieldAuthScreen);
final passwordFieldFinder = find.byKey(AppKeys.passwordTextFieldAuthScreen);
final signInButtonFinder = find.byKey(AppKeys.signInButtonAuthScreen);
expect(emailFieldFinder, findsOneWidget, reason: 'Email text field should be found.');
expect(passwordFieldFinder, findsOneWidget, reason: 'Password text field should be found.');
expect(signInButtonFinder, findsOneWidget, reason: 'Sign In button should be found.');
await tester.enterText(emailFieldFinder, 'reshetnick.mike+user4@gmail.com');
await tester.enterText(passwordFieldFinder, '1234567890');
await tester.pumpAndSettle();
await tester.tap(signInButtonFinder);
await tester.pumpAndSettle(const Duration(seconds: 3));
expect(signInButtonFinder, findsNothing, reason: 'Sign-in button should not be visible after successful login and navigation.');
});
}

View File

@@ -0,0 +1,31 @@
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:eatvio_app/main.dart' as app;
import '../common_cases/skip_welcome_screen.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
await AppInitializer.initialize(buildType: AppBuildType.tests);
});
testWidgets('tapping logo 7 times opens dev tools', (WidgetTester tester) async {
await tester.pumpWidget(const app.MyApp());
await tester.pumpAndSettle(const Duration(seconds: 3));
await skipWelcomeScreen(tester);
final logoButtonFinder = find.byKey(AppKeys.hiddenLogoButtonAuthScreen);
expect(logoButtonFinder, findsOneWidget, reason: 'The logo GestureDetector should be found in auth screen');
for (int i = 0; i < 7; i++) {
await tester.tap(logoButtonFinder);
await tester.pump(const Duration(milliseconds: 50));
}
await tester.pumpAndSettle();
expect(find.byType(DevToolsScreen), findsOneWidget, reason: 'DevToolsScreen should be visible after tapping the logo 7 times.');
});
}

View File

@@ -0,0 +1,8 @@
import 'package:eatvio_app/app_keys.dart';
import 'package:flutter_test/flutter_test.dart';
Future<void> skipWelcomeScreen(WidgetTester tester) async {
final welcomeScreenButtonFinder = find.byKey(AppKeys.welcomeScreenButton);
await tester.tap(welcomeScreenButtonFinder);
await tester.pumpAndSettle();
}

13
lib/app_keys.dart Normal file
View File

@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class AppKeys {
// Auth Screen
static const hiddenLogoButtonAuthScreen = ValueKey('hiddenLogoButtonAuthScreen');
static const emailTextFieldAuthScreen = ValueKey('emailTextFieldAuthScreen');
static const passwordTextFieldAuthScreen = ValueKey('passwordTextFieldAuthScreen');
static const signInButtonAuthScreen = ValueKey('signInButtonAuthScreen');
// Welcome Screen
static const welcomeScreenButton = ValueKey('welcomeScreenButton');
}

View File

@@ -1,3 +1,5 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
@@ -13,8 +15,24 @@ import 'package:flutter_localizations/flutter_localizations.dart';
/// Конфигурация приложения
class AppConfig {
AppConfig({required this.buildType}){
_baseUrl = switch (buildType) {
AppBuildType.prod => EnvConfig.baseUrl,
AppBuildType.tests => EnvConfig.devBaseUrl,
AppBuildType.dev => EnvConfig.devBaseUrl,
};
}
final AppBuildType buildType;
late final String _baseUrl;
String get baseUrl => _baseUrl;
void setBaseUrl(String baseUrls) {
_baseUrl = baseUrls;
}
/// Список глобальных BlocProvider'ов
static List<BlocProvider> get globalBlocProviders => [
List<BlocProvider> get globalBlocProviders => [
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()..getNotifications()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
@@ -25,7 +43,7 @@ class AppConfig {
];
/// Делегаты локализации
static const List<LocalizationsDelegate> localizationDelegates = [
List<LocalizationsDelegate> get localizationDelegates => [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
@@ -33,5 +51,5 @@ class AppConfig {
];
/// Поддерживаемые локали
static const List<Locale> supportedLocales = [Locale.fromSubtags(languageCode: 'ru')];
List<Locale> get supportedLocales => [const Locale.fromSubtags(languageCode: 'ru')];
}

View File

@@ -1,6 +1,8 @@
import 'dart:io' as io;
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/locator_service.dart' as di;
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
@@ -13,11 +15,11 @@ import 'package:sentry_flutter/sentry_flutter.dart';
/// Класс для инициализации приложения
class AppInitializer {
/// Инициализация всех сервисов приложения
static Future<void> initialize() async {
await _initializeSentry();
static Future<void> initialize({required AppBuildType buildType}) async {
await _initializeEnvironment();
await _initializeDependencyInjection(buildType: buildType);
await _initializeSentry();
await _initializeFirebase();
await _initializeDependencyInjection();
await _initializeDebugMode();
await _initializeAnalytics();
}
@@ -36,12 +38,14 @@ class AppInitializer {
/// Инициализация Firebase
static Future<void> _initializeFirebase() async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await FirebaseMessaging.instance.requestPermission();
if (sl<AppConfig>().buildType == AppBuildType.prod) {
await FirebaseMessaging.instance.requestPermission();
}
}
/// Инициализация системы внедрения зависимостей
static Future<void> _initializeDependencyInjection() async {
await di.init();
static Future<void> _initializeDependencyInjection({required AppBuildType buildType}) async {
await di.init(buildType: buildType);
}
/// Настройка режима отладки
@@ -66,3 +70,5 @@ class AppInitializer {
}, appRunner: appRunner);
}
}
enum AppBuildType { prod, tests, dev }

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
import 'package:eatvio_app/core/routing/other_routes.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/routing/settings_routes.dart';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart';
@@ -50,4 +51,8 @@ class AppRouter {
return null;
}
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {
appRouter.goNamed('registerViaSocialScreen', extra: {'authResult': authResult, 'provider': provider});
}
}

View File

@@ -1,6 +1,3 @@
import 'package:eatvio_app/core/env_config.dart';
String baseUrl = EnvConfig.devBaseUrl;
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
const buildDate = String.fromEnvironment('BUILD_DATE', defaultValue: '');

View File

@@ -0,0 +1,12 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:get_it/get_it.dart';
class AppConfigRegistration {
static final sl = GetIt.instance;
/// Регистрация AppConfig
static void registerAppConfig({required AppBuildType buildType}) {
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: buildType));
}
}

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
@@ -75,7 +76,7 @@ class CubitRegistrations {
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
sl.registerFactory<OAuthCubit>(() => OAuthCubit(authRepository: sl()));
// Feed related cubits
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:get_it/get_it.dart';
@@ -18,7 +18,7 @@ class ExternalRegistrations {
sl.registerLazySingleton(
() => Dio(
BaseOptions(
baseUrl: baseUrl,
baseUrl: sl<AppConfig>().baseUrl,
headers: {
'accept': 'application/json',
'content-type': 'application/json',

View File

@@ -1,5 +1,7 @@
import 'dart:developer';
import 'dart:io';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:flutter/services.dart';
@@ -19,7 +21,9 @@ class FirebaseApi {
if (await _isHuaweiDevice()) {
token = await _getHuaweiPushTokenNative();
} else {
await _firebaseMessaging.requestPermission();
if ((sl<AppConfig>().buildType == AppBuildType.prod)) {
await _firebaseMessaging.requestPermission();
}
token = await _firebaseMessaging.getToken();
}
return token;

View File

@@ -1,3 +1,5 @@
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/di/app_config_registration.dart';
import 'package:eatvio_app/core/di/cubit_registrations.dart';
import 'package:eatvio_app/core/di/external_registrations.dart';
import 'package:eatvio_app/core/di/repository_registrations.dart';
@@ -9,19 +11,22 @@ import 'package:get_it/get_it.dart';
final sl = GetIt.instance;
/// Инициализация системы внедрения зависимостей
Future<void> init() async {
Future<void> init({required AppBuildType buildType}) async {
// Регистрация AppConfig
AppConfigRegistration.registerAppConfig(buildType: buildType);
// Регистрация внешних зависимостей (Dio, etc.)
ExternalRegistrations.registerAll();
// Регистрация сервисов
ServiceRegistrations.registerAll();
// Регистрация репозиториев
RepositoryRegistrations.registerAll();
// Регистрация Cubit'ов
CubitRegistrations.registerAll();
// Регистрация WebSocket менеджера
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
}
}

View File

@@ -1,6 +1,8 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/register_via_social_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
@@ -32,6 +34,14 @@ class AuthRoutes {
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
),
GoRoute(path: RouteConstants.verifyEmailScreen, name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
GoRoute(
path: RouteConstants.registerViaSocialScreen,
name: 'registerViaSocialScreen',
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
return RegisterViaSocialScreen(authResult: extra?['authResult'] as AuthResultEntity?, provider: extra?['provider'] as String?);
},
),
];
}
}

View File

@@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
@@ -76,7 +76,7 @@ class OtherRoutes {
builder: (context, state) => const UpdateScreen(),
redirect: (context, state) async {
try {
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
final versions = (await sl<Dio>().get('${sl<AppConfig>().baseUrl}/api/app_versions')).data;
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version;
Version currentVersion = Version.parse(version);

View File

@@ -7,6 +7,7 @@ class RouteConstants {
static const String scanQRCodeScreen = 'scan-qr-code-screen';
static const String verifyScreen = '/verify-screen';
static const String verifyEmailScreen = '/verify-email-screen';
static const String registerViaSocialScreen = '/register-via-social-screen';
// Main navigation routes
static const String myProfileScreen = '/my-profile-screen';

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
@@ -111,7 +112,7 @@ class WsManager {
_privateNotificationChannel = client?.privateChannel(
'$newNotificationsChannelName.$userId',
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
headers: {
'Authorization': 'Bearer $token',
},
@@ -124,7 +125,7 @@ class WsManager {
_privateMessagesChannel = client?.privateChannel(
'$newMessagesChannelName.$userId',
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
headers: {
'Authorization': 'Bearer $token',
},
@@ -137,7 +138,7 @@ class WsManager {
_privateDirectChatChannel = client?.privateChannel(
'$directChatChannelName.$chatId',
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
authorizationEndpoint: Uri.parse('$baseUrl/broadcasting/auth'),
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
headers: {
'Authorization': 'Bearer $token',
},

View File

@@ -1,4 +1,4 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -86,7 +86,7 @@ class CustomPopup extends StatelessWidget {
if (isAuthor) {
addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () {
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
showInfoToast(s.link_was_copied);
});
addItem('assets/icons/share2.svg', 'Поделиться', () {
@@ -94,7 +94,7 @@ class CustomPopup extends StatelessWidget {
SharePlus.instance.share(
ShareParams(
uri: Uri.parse(
'$baseUrl/ru/$path',
'${sl<AppConfig>().baseUrl}/ru/$path',
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
@@ -140,7 +140,7 @@ class CustomPopup extends StatelessWidget {
});
} else {
addItem('assets/icons/copy2.svg', 'Скопировать ссылку', () {
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
showInfoToast(s.link_was_copied);
});
addItem('assets/icons/share2.svg', 'Поделиться', () {
@@ -148,7 +148,7 @@ class CustomPopup extends StatelessWidget {
SharePlus.instance.share(
ShareParams(
uri: Uri.parse(
'$baseUrl/ru/$path',
'${sl<AppConfig>().baseUrl}/ru/$path',
),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),

View File

@@ -0,0 +1,24 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_model.freezed.dart';
part 'oauth_model.g.dart';
@freezed
abstract class OAuthModel with _$OAuthModel {
const factory OAuthModel({
required String provider,
required String id,
@JsonKey(name: 'access_token') required String accessToken,
@JsonKey(name: 'refresh_token') required String refreshToken,
@JsonKey(name: 'first_name') required String firstName,
@JsonKey(name: 'last_name') required String lastName,
required String email,
required String phone,
@JsonKey(name: 'date_of_birth') required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) = _OAuthModel;
factory OAuthModel.fromJson(Map<String, dynamic> json) => _$OAuthModelFromJson(json);
}

View File

@@ -0,0 +1,44 @@
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class OAuthCubit extends Cubit<OAuthState> {
final AuthRepository authRepository;
OAuthCubit({required this.authRepository}) : super(const OAuthState());
Future<void> getOAuthToken({
required String provider,
required String accessToken,
required String id,
required String refreshToken,
required String firstName,
required String lastName,
required String email,
required String phone,
required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) async {
try {
final token = await authRepository.getOAuthToken(
provider: provider,
accessToken: accessToken,
id: id,
refreshToken: refreshToken,
firstName: firstName,
lastName: lastName,
email: email,
phone: phone,
dateOfBirth: dateOfBirth,
gender: gender,
weight: weight,
height: height,
);
emit(OAuthState.loaded(token: token));
} catch (e) {
emit(OAuthState.error(errorMessage: e.toString()));
}
}
}

View File

@@ -0,0 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'oauth_state.freezed.dart';
@freezed
abstract class OAuthState with _$OAuthState {
const factory OAuthState() = _OAuthState;
const factory OAuthState.loading() = OAuthStateLoading;
const factory OAuthState.loaded({required String token}) = OAuthStateLoaded;
const factory OAuthState.error({required String errorMessage}) = OAuthStateError;
}

View File

@@ -2,6 +2,8 @@
import 'dart:async';
import 'dart:developer';
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
@@ -46,7 +48,7 @@ class _AuthScreenState extends State<AuthScreen> {
late StreamSubscription<bool> keyboardSubscription;
bool isFocused = false;
int tapsCount = 0;
bool _isLoading = false;
@override
void initState() {
super.initState();
@@ -92,6 +94,7 @@ class _AuthScreenState extends State<AuthScreen> {
@override
Widget build(BuildContext context) {
var shortestSide = MediaQuery.sizeOf(context).shortestSide;
final screenSize = MediaQuery.of(context).size;
final bool useMobileLayout = shortestSide < 600;
if (!useMobileLayout) isFocused = true;
final s = S.of(context);
@@ -102,9 +105,10 @@ class _AuthScreenState extends State<AuthScreen> {
builder: (BuildContext context, AuthState state) {
return Scaffold(
appBar: AppBar(
toolbarHeight: screenSize.height * 0.15,
centerTitle: true,
toolbarHeight: isFocused ? 64 : MediaQuery.of(context).size.width - 64,
title: GestureDetector(
key: AppKeys.hiddenLogoButtonAuthScreen,
onTap: () {
tapsCount++;
if (tapsCount == 7) {
@@ -112,7 +116,7 @@ class _AuthScreenState extends State<AuthScreen> {
GoRouter.of(context).pushNamed(DevToolsScreen.name);
}
},
child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: isFocused ? 64 : MediaQuery.of(context).size.width - 64, fit: BoxFit.contain),
child: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: screenSize.height * 0.20),
),
),
body: SafeArea(
@@ -122,8 +126,8 @@ class _AuthScreenState extends State<AuthScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Social auth buttons at the top (outlined)
const Text('Авторизация через:', style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 8),
const Center(child: Text('Авторизация через')),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -164,6 +168,7 @@ class _AuthScreenState extends State<AuthScreen> {
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
textInputType: TextInputType.emailAddress,
key: AppKeys.emailTextFieldAuthScreen,
hintText: 'Введите email',
),
const SizedBox(height: 16),
@@ -172,6 +177,7 @@ class _AuthScreenState extends State<AuthScreen> {
CustomTextFormField(
obscureText: hidePassword,
controller: passwordController,
key: AppKeys.passwordTextFieldAuthScreen,
hintText: 'Введите пароль',
suffix: IconButton(
onPressed: () {
@@ -224,7 +230,7 @@ class _AuthScreenState extends State<AuthScreen> {
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/oferta_bs.pdf')),
),
const TextSpan(text: ' и '),
],
@@ -236,7 +242,7 @@ class _AuthScreenState extends State<AuthScreen> {
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/docs/privacy.pdf')),
),
],
),
@@ -249,21 +255,28 @@ class _AuthScreenState extends State<AuthScreen> {
height: 48,
width: MediaQuery.of(context).size.width - 88,
child: FloatingActionButton.extended(
key: AppKeys.signInButtonAuthScreen,
heroTag: 'authButton',
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
onPressed: state is AuthStateLoading
backgroundColor: (state is AuthStateLoading || _isLoading) ? Colors.black.withAlpha(50) : appTheme.blueA400,
onPressed: (state is AuthStateLoading || _isLoading)
? null
: () async {
setState(() {
_isLoading = true;
});
final isAuthenticated = await authCubit.getTokenByCredentials(emailController.text, passwordController.text);
if (isAuthenticated) {
if (context.mounted) {
_reconnectWebSocket(context);
await _reconnectWebSocket(context);
}
}
setState(() {
_isLoading = false;
});
},
label: state is AuthStateLoading
? const CupertinoActivityIndicator()
@@ -296,7 +309,7 @@ class _AuthScreenState extends State<AuthScreen> {
children: [
const Text('Где взять QR-код?\n', style: TextStyle(fontSize: 18)),
Text(
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
'1. Войдите на сайте ${sl<AppConfig>().baseUrl} в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"',
),
const SizedBox(height: 12),
Row(
@@ -391,10 +404,14 @@ class _SocialButton extends StatelessWidget {
void _onVkPressed() async {
try {
final accessToken = await SocialAuthFacade.signInWithVk();
if (accessToken == null) {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return;
final authResult = await SocialAuthFacade.signInWithVkAndFetchProfile(context: context);
if (authResult == null) {
return;
}
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'vk');
} catch (e) {
log(e.toString());
}
@@ -402,10 +419,14 @@ void _onVkPressed() async {
void _onOkPressed() async {
try {
final accessToken = await SocialAuthFacade.signInWithOk();
if (accessToken == null) {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return;
final authResult = await SocialAuthFacade.signInWithOkAndFetchProfile();
if (authResult == null) {
return;
}
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'ok');
} catch (e) {
log(e.toString());
}
@@ -413,18 +434,27 @@ void _onOkPressed() async {
void _onMailRuPressed() async {
try {
final accessToken = await SocialAuthFacade.signInWithMailRu();
if (accessToken == null) {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return;
final authResult = await SocialAuthFacade.signInWithMailRuAndFetchProfile();
if (authResult == null) {
return;
}
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'mailru');
} catch (e) {
log(e.toString());
}
}
void _onYandexPressed() async {
final accessToken = await SocialAuthFacade.signInWithYandex();
if (accessToken == null) return;
try {
final authResult = await SocialAuthFacade.signInWithYandex();
if (authResult == null) return;
AppRouter.navigateToRegisterViaSocial(authResult: authResult, provider: 'yandex');
} catch (e) {
log(e.toString());
}
}
Future<void> _reconnectAfterAuth() async {

View File

@@ -0,0 +1,521 @@
import 'package:eatvio_app/core/const.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/core/di/cubit_registrations.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/websockets/ws_manager.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/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
import 'package:url_launcher/url_launcher.dart';
class RegisterViaSocialScreen extends StatefulWidget {
final AuthResultEntity? authResult;
final String? provider;
const RegisterViaSocialScreen({super.key, this.authResult, this.provider});
@override
State<RegisterViaSocialScreen> createState() => _RegisterViaSocialScreenState();
}
class _RegisterViaSocialScreenState extends State<RegisterViaSocialScreen> {
final maskFormatter = MaskTextInputFormatter(mask: '+7 (###) ### ## ##', filter: {"#": RegExp(r'[0-9]')});
final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')});
// Контроллеры для полей ввода
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final dateOfBirthController = TextEditingController();
final weightController = TextEditingController();
final heightController = TextEditingController();
// Состояние для выбора пола
bool isMaleSelected = true;
@override
void initState() {
super.initState();
_initializeFieldsFromAuthResult();
}
Future<void> _reconnectWebSocket(BuildContext context) async {
try {
await sl<WsManager>().clear();
await sl<WsManager>().initialize();
} catch (e) {
rethrow;
}
if (context.mounted) {
try {
await sl<ProfileAvatarCubit>().getMyProfile();
await sl<ChatsCubit>().getChatRoomsWithLoading();
await sl<ChatsCubit>().connectToWebSocket();
await sl<NotificationsCubit>().getNotifications();
await sl<NotificationsCubit>().connectToWebSocket();
await sl<FirebaseApi>().registerPushToken();
showSuccessToast('Успешная регистрация');
} catch (e) {
rethrow;
}
}
AppRouter.appRouter.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
}
void _initializeFieldsFromAuthResult() {
if (widget.authResult != null) {
final authResult = widget.authResult!;
// Логируем полученные данные для отладки
print('AuthResult data:');
print('firstName: ${authResult.firstName}');
print('lastName: ${authResult.lastName}');
print('email: ${authResult.email}');
print('phone: ${authResult.phone}');
print('birthDate: ${authResult.birthDate}');
print('gender: ${authResult.gender}');
print('provider: ${widget.provider}');
// Предзаполняем поля из AuthResultEntity
if (authResult.firstName != null) {
firstNameController.text = authResult.firstName!;
}
if (authResult.lastName != null) {
lastNameController.text = authResult.lastName!;
}
if (authResult.email != null) {
emailController.text = authResult.email!;
}
if (authResult.phone != null) {
phoneController.text = authResult.phone!;
}
if (authResult.birthDate != null) {
dateOfBirthController.text = _formatBirthDate(authResult.birthDate!);
}
if (authResult.gender != null) {
isMaleSelected = authResult.gender == GenderEnum.male;
}
}
}
String _formatBirthDate(String birthDate) {
try {
// Пытаемся распарсить различные форматы даты
DateTime? date;
// Если дата в формате ISO (YYYY-MM-DD)
if (birthDate.contains('-')) {
date = DateTime.tryParse(birthDate);
}
// Если дата в формате DD.MM.YYYY
else if (birthDate.contains('.')) {
final parts = birthDate.split('.');
if (parts.length == 3) {
final day = int.tryParse(parts[0]);
final month = int.tryParse(parts[1]);
final year = int.tryParse(parts[2]);
if (day != null && month != null && year != null) {
date = DateTime(year, month, day);
}
}
}
if (date != null) {
// Форматируем в DD.MM.YYYY
final day = date.day.toString().padLeft(2, '0');
final month = date.month.toString().padLeft(2, '0');
final year = date.year.toString();
return '$day.$month.$year';
}
} catch (e) {
// Если не удалось распарсить, возвращаем исходную строку
}
return birthDate;
}
String _getProviderName(String? provider) {
switch (provider) {
case 'vk':
return 'ВКонтакте';
case 'yandex':
return 'Яндекс';
case 'ok':
return 'Одноклассники';
case 'mailru':
return 'Mail.ru';
default:
return 'социальной сети';
}
}
String _getDataStatusMessage(AuthResultEntity authResult) {
final hasEmail = authResult.email != null && authResult.email!.isNotEmpty;
final hasPhone = authResult.phone != null && authResult.phone!.isNotEmpty;
final hasBirthDate = authResult.birthDate != null && authResult.birthDate!.isNotEmpty;
if (hasEmail && hasPhone && hasBirthDate) {
return 'Все данные успешно получены и предзаполнены.';
} else if (hasEmail && hasPhone) {
return 'Email и номер телефона получены. Дата рождения может быть недоступна из-за настроек приватности.';
} else if (hasEmail) {
return 'Email получен. Номер телефона и дата рождения могут быть недоступны из-за настроек приватности.';
} else if (hasPhone) {
return 'Номер телефона получен. Email и дата рождения могут быть недоступны из-за настроек приватности.';
} else {
// Специальное сообщение для VK
if (widget.provider == 'vk') {
return 'Основные данные получены. Email запрашивается автоматически. Номер телефона получается через VK API. Если номер замаскирован, это означает, что пользователь ограничил доступ к нему в настройках приватности.';
} else {
return 'Основные данные получены. Email, номер телефона и дата рождения могут быть недоступны из-за настроек приватности.';
}
}
}
@override
void dispose() {
firstNameController.dispose();
lastNameController.dispose();
emailController.dispose();
phoneController.dispose();
dateOfBirthController.dispose();
weightController.dispose();
heightController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider<OAuthCubit>(
create: (context) => CubitRegistrations.sl<OAuthCubit>(),
child: Scaffold(
appBar: AppBar(toolbarHeight: 0),
body: BlocBuilder<OAuthCubit, OAuthState>(
builder: (context, state) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
const Center(
child: Text(
'Регистрация',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1, color: Color(0xFF323F4B)),
),
),
const SizedBox(height: 24),
Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Имя'),
const SizedBox(height: 4),
CustomTextFormField(controller: firstNameController, hintText: 'Введите имя', onChanged: (newValue) {}),
],
),
),
const SizedBox(width: 12),
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Фамилия'),
const SizedBox(height: 4),
CustomTextFormField(controller: lastNameController, hintText: 'Введите фамилию', onChanged: (newValue) {}),
],
),
),
],
),
const SizedBox(height: 12),
const Text('Почта'),
const SizedBox(height: 4),
CustomTextFormField(
controller: emailController,
textInputType: TextInputType.emailAddress,
keyboardType: TextInputType.emailAddress,
hintText: 'Введите email',
textInputAction: TextInputAction.next,
onChanged: (newValue) {},
),
const SizedBox(height: 12),
const Text('Номер телефона'),
const SizedBox(height: 4),
CustomTextFormField(
textInputAction: TextInputAction.done,
hintText: '+7 (999) 999 99 99',
controller: phoneController,
keyboardType: TextInputType.number,
inputFormatters: [maskFormatter],
onChanged: (newValue) {},
),
const SizedBox(height: 12),
Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Дата рождения'),
const SizedBox(height: 4),
CustomTextFormField(
textInputAction: TextInputAction.done,
hintText: '01.01.2000',
controller: dateOfBirthController,
keyboardType: TextInputType.number,
inputFormatters: [maskFormatterForBirthday],
onChanged: (newValue) {},
),
],
),
),
const SizedBox(width: 12),
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Пол'),
const SizedBox(height: 4),
Row(
children: [
Expanded(child: _genderButton('М', isMaleSelected)),
const SizedBox(width: 4),
Expanded(child: _genderButton('Ж', !isMaleSelected)),
],
),
],
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Вес'),
const SizedBox(height: 4),
CustomTextFormField(controller: weightController, hintText: 'Введите вес', onChanged: (newValue) {}),
],
),
),
const SizedBox(width: 12),
SizedBox(
width: MediaQuery.of(context).size.width / 2 - 22,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Рост'),
const SizedBox(height: 4),
CustomTextFormField(controller: heightController, hintText: 'Введите рост', onChanged: (newValue) {}),
],
),
),
],
),
const SizedBox(height: 28),
const Center(
child: Text(
'Проверьте правильность заполненных\nданных. Пол, рост и вес требуются для\nрасчета вашей дневной нормы калорий',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 28),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _handleRegistration(context),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
height: 52,
decoration: BoxDecoration(color: state is OAuthStateLoading ? Colors.grey : appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
child: Center(
child: state is OAuthStateLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
'Зарегистрироваться',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18, color: Colors.white),
),
),
),
),
const SizedBox(height: 16),
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'Нажимая "Зарегистрироваться", Вы соглашаетесь\n',
style: const TextStyle(fontSize: 16, color: Colors.black),
children: [
const TextSpan(
text: 'c ',
style: TextStyle(fontSize: 16, color: Colors.black),
),
if (!hidePlans) ...[
TextSpan(
text: ' Публичной офертой',
style: TextStyle(
fontSize: 16,
color: appTheme.blueA400,
decorationColor: appTheme.blueA400,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/oferta_bs.pdf')),
),
const TextSpan(
text: ' и ',
style: TextStyle(fontSize: 16, color: Colors.black),
),
],
TextSpan(
text: 'Политикой конфиденциальности и защитой персональных данных',
style: TextStyle(fontSize: 16, color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
),
],
),
),
),
// Показываем ошибки если есть
if (state is OAuthStateError) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red.shade200),
),
child: Text(
state.errorMessage,
style: TextStyle(color: Colors.red.shade700),
textAlign: TextAlign.center,
),
),
],
// Обработка успешной регистрации
if (state is OAuthStateLoaded) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green.shade200),
),
child: const Text(
'Регистрация успешно завершена!',
style: TextStyle(color: Colors.green),
textAlign: TextAlign.center,
),
),
// Автоматический переход на главный экран после успешной регистрации
Builder(
builder: (context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_reconnectWebSocket(context);
});
return const SizedBox.shrink();
},
),
],
],
),
);
},
),
),
);
}
Widget _genderButton(String gender, bool isActive) {
return InkWell(
onTap: () {
setState(() {
isMaleSelected = gender == 'М';
});
},
borderRadius: BorderRadius.circular(20),
child: Ink(
height: 52,
decoration: BoxDecoration(
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
border: isActive ? Border.all(width: 2, color: appTheme.blueA400) : null,
),
child: Center(
child: Text(
gender,
style: TextStyle(color: isActive ? appTheme.blueA400 : const Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500),
),
),
),
);
}
void _handleRegistration(BuildContext context) {
// Валидация полей
if (firstNameController.text.isEmpty ||
lastNameController.text.isEmpty ||
emailController.text.isEmpty ||
phoneController.text.isEmpty ||
dateOfBirthController.text.isEmpty ||
weightController.text.isEmpty ||
heightController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Пожалуйста, заполните все поля')));
return;
}
// Парсим числовые значения
int? weight;
int? height;
try {
weight = int.parse(weightController.text);
height = int.parse(heightController.text);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Вес и рост должны быть числами')));
return;
}
// Вызываем OAuth регистрацию
context.read<OAuthCubit>().getOAuthToken(
provider: widget.provider ?? 'social',
accessToken: widget.authResult?.accessToken ?? 'dummy_token',
id: widget.authResult?.userId ?? 'dummy_id',
refreshToken: widget.authResult?.refreshToken ?? 'dummy_refresh_token',
firstName: firstNameController.text,
lastName: lastNameController.text,
email: emailController.text,
phone: phoneController.text,
dateOfBirth: dateOfBirthController.text,
gender: isMaleSelected ? 'М' : 'Ж',
weight: weight,
height: height,
);
}
}

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
@@ -40,17 +40,11 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Сканирование QR-кода'),
centerTitle: true,
),
appBar: AppBar(title: const Text('Сканирование QR-кода'), centerTitle: true),
body: Column(
children: <Widget>[
Expanded(
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
),
child: QRView(key: qrKey, onQRViewCreated: _onQRViewCreated),
),
],
),
@@ -68,17 +62,9 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
final scannedUrl = scanData.code;
final uri = Uri.parse(scannedUrl ?? '');
final scannedBaseUrl = (uri.hasAuthority) ? '${uri.scheme}://${uri.host}' : '';
baseUrl = scannedBaseUrl;
sl<Dio>().options.baseUrl = baseUrl;
final response = await Dio().getUri(
uri,
options: Options(
headers: {
'accept': 'application/json',
'content-type': 'application/json',
},
),
);
sl<AppConfig>().setBaseUrl(scannedBaseUrl);
sl<Dio>().options.baseUrl = scannedBaseUrl;
final response = await Dio().getUri(uri, options: Options(headers: {'accept': 'application/json', 'content-type': 'application/json'}));
final token = response.data['token'];
await TokenStorage.saveTokenInStorage(token);
try {
@@ -97,10 +83,7 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
} catch (e) {
rethrow;
}
AppRouter.navigatorKey.currentContext?.go(
'/feed-screen',
extra: {'needToInitializeWebSocket': false},
);
AppRouter.navigatorKey.currentContext?.go('/feed-screen', extra: {'needToInitializeWebSocket': false});
showSuccessToast('Добро пожаловать!');
} catch (e) {
await controller.resumeCamera();

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:eatvio_app/features/auth/models/social_exchange_request.dart';
@@ -8,26 +9,17 @@ class AuthRepository {
AuthRepository({required this.authService});
Future<TokenModel> getTokenByCredentials(
String email,
String password,
) async {
Future<TokenModel> getTokenByCredentials(String email, String password) async {
try {
return await authService.getTokenByCredentials(
credentials: CredentialsModel(
email: email,
password: password,
),
credentials: CredentialsModel(email: email, password: password),
);
} catch (e) {
rethrow;
}
}
Future<TokenModel> getTokenBySocial({
required String provider,
required String accessToken,
}) async {
Future<TokenModel> getTokenBySocial({required String provider, required String accessToken}) async {
try {
return await authService.getTokenBySocial(
body: SocialExchangeRequest(provider: provider, accessToken: accessToken),
@@ -36,4 +28,40 @@ class AuthRepository {
rethrow;
}
}
Future<String> getOAuthToken({
required String provider,
required String accessToken,
required String id,
required String refreshToken,
required String firstName,
required String lastName,
required String email,
required String phone,
required String dateOfBirth,
required String gender,
required int weight,
required int height,
}) async {
try {
return (await authService.getOAuthToken(
body: OAuthModel(
provider: provider,
accessToken: accessToken,
id: id,
refreshToken: refreshToken,
firstName: firstName,
lastName: lastName,
email: email,
phone: phone,
dateOfBirth: dateOfBirth,
gender: gender == 'М' ? 'male' : 'female',
weight: weight,
height: height,
),
))['token']! as String;
} catch (e) {
rethrow;
}
}
}

View File

@@ -27,7 +27,7 @@ class SocialAuthFacade {
try {
final controller = VkIDController(clID: int.parse(clientIdStr));
final authUri = controller.generateAuthorizeLinkWithCodeVerifier(scopes: [VkScope.phone, VkScope.email, VkScope.personalInfo]);
final authUri = controller.generateAuthorizeLinkWithCodeVerifier(provider: VkProvider.vkID, scopes: [VkScope.email, VkScope.phone]);
final authUrl = authUri.value.toString();
final codeVerifier = authUri.key;
@@ -114,18 +114,16 @@ class SocialAuthFacade {
return null;
}
static Future<String?> signInWithOk() async {
static Future<AuthResultEntity?> signInWithOkAndFetchProfile() async {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
final vkAuthRes = await _signInWithVkIdProvider(context: context, provider: VkProvider.okRu);
return vkAuthRes?.accessToken;
return await _signInWithVkIdProvider(context: context, provider: VkProvider.okRu);
}
static Future<String?> signInWithMailRu() async {
static Future<AuthResultEntity?> signInWithMailRuAndFetchProfile() async {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
final vkAuthRes = await _signInWithVkIdProvider(context: context, provider: VkProvider.mailRu);
return vkAuthRes?.accessToken;
return await _signInWithVkIdProvider(context: context, provider: VkProvider.mailRu);
}
static Future<AuthResultEntity?> signInWithYandex() async {

View File

@@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
import 'package:eatvio_app/features/auth/models/oauth_model.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/models/social_exchange_request.dart';
import 'package:retrofit/retrofit.dart';
@@ -19,4 +20,9 @@ abstract class AuthService {
Future<TokenModel> getTokenBySocial({
@Body() required SocialExchangeRequest body,
});
@POST('/api/oauth/callback')
Future<Map<String, Object?>> getOAuthToken({
@Body() required OAuthModel body,
});
}

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
@@ -207,7 +207,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
children: [
CircleAvatar(
radius: 16,
foregroundImage: NetworkImage(baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? '')),
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? '')),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
const SizedBox(width: 8),
@@ -239,7 +239,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
),
const SizedBox(height: 8),
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
CachedNetworkImage(imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}'),
CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${blogPostState.blogPostModel?.image ?? ''}'),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
@@ -267,7 +267,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/blog_post/${widget.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/app_decoration.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
@@ -304,7 +304,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image(
image: NetworkImage('$baseUrl/storage/${widget.feedBlogPostModel!.image}'),
image: NetworkImage('${sl<AppConfig>().baseUrl}/storage/${widget.feedBlogPostModel!.image}'),
),
),
),

View File

@@ -1,7 +1,8 @@
import 'dart:developer';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.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';
@@ -36,7 +37,7 @@ class BlogPostRepository {
Future<XFile?> downloadImage(String imageUrl) async {
try {
// Загрузка изображения
final response = await http.get(Uri.parse('$baseUrl/storage/$imageUrl'));
final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
if (response.statusCode == 200) {
// Получение временной директории

View File

@@ -1,5 +1,6 @@
import 'package:badges/badges.dart' as badges;
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
@@ -102,7 +103,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
label: '',
icon: CircleAvatar(
foregroundImage: NetworkImage(
widget.profileModel.profilePhotoUrl.contains('https') ? widget.profileModel.profilePhotoUrl : baseUrl + widget.profileModel.profilePhotoUrl,
widget.profileModel.profilePhotoUrl.contains('https') ? widget.profileModel.profilePhotoUrl : sl<AppConfig>().baseUrl + widget.profileModel.profilePhotoUrl,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: 16,

View File

@@ -1,6 +1,7 @@
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/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/app_decoration.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@@ -88,7 +89,7 @@ class _PhotoViewScreenState extends State<PhotoViewScreen> {
color: Colors.white,
),
title: Text(
widget.imageUrls[index].replaceAll('$baseUrl/storage/chat_files/', ''),
widget.imageUrls[index].replaceAll('${sl<AppConfig>().baseUrl}/storage/chat_files/', ''),
style: const TextStyle(
color: Colors.white,
fontSize: 12,

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
@@ -47,7 +48,7 @@ class _OrderCardState extends State<OrderCard> {
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
'$baseUrl/storage/${widget.plan.image}',
'${sl<AppConfig>().baseUrl}/storage/${widget.plan.image}',
width: double.infinity,
fit: BoxFit.cover,
),

View File

@@ -1,4 +1,6 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -49,7 +51,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: TextButton(
onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '$baseUrl/docs/privacy.pdf' : '$baseUrl/oferta_bs.pdf')),
onPressed: () async => await launchUrl(Uri.parse(hidePlans ? '${sl<AppConfig>().baseUrl}/docs/privacy.pdf' : '${sl<AppConfig>().baseUrl}/oferta_bs.pdf')),
child: Text(
hidePlans ? 'Политика конфиденциальности и пользовательское соглашение' : 'Договор офёрты',
style: const TextStyle(height: 1, fontSize: 16, color: Color(0xFF323F4B), decoration: TextDecoration.underline),

View File

@@ -2,8 +2,10 @@ import 'dart:convert';
import 'dart:io';
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
@@ -53,7 +55,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
name: diet.name,
duration: diet.duration,
type: type,
imageUrl: '$baseUrl/storage/${diet.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${diet.image}',
),
);
emit(state.copyWith(dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));

View File

@@ -1,7 +1,8 @@
import 'dart:developer';
import 'dart:io';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart' as i;
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
@@ -187,13 +188,13 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
}
Future<void> recipeToEntity(Recipe recipe) async {
final file = await downloadImageAsFile('$baseUrl/storage/${recipe.image}');
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}');
final List<StepEntity> steps = await Future.wait(
recipe.steps!.map(
(e) async => StepEntity(
description: e.description,
image: e.image.isEmpty ? null : await downloadImageAsFile('$baseUrl/storage/${e.image}'),
image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'),
),
),
);

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
@@ -84,7 +84,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
void initState() {
super.initState();
setState(() {
baseUrlForTest = baseUrl;
baseUrlForTest = sl<AppConfig>().baseUrl;
});
testsStates = List.generate(testCases.length, (index) => TestState.notRun);
}
@@ -111,8 +111,8 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
onPressed: () {
setState(() {
baseUrlForTest = controller.text;
baseUrl = baseUrlForTest.toLowerCase();
sl<Dio>().options.baseUrl = baseUrl;
sl<AppConfig>().setBaseUrl(baseUrlForTest.toLowerCase());
sl<Dio>().options.baseUrl = baseUrlForTest.toLowerCase();
controller.clear();
});
},
@@ -122,23 +122,23 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
),
const SizedBox(height: 10),
CustomOutlinedButton(
text: baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url',
text: sl<AppConfig>().baseUrl == EnvConfig.devBaseUrl ? 'Set production base url' : 'Set dev2 base url',
onPressed: () {
setState(() {
if (baseUrl == EnvConfig.baseUrl) {
if (sl<AppConfig>().baseUrl == EnvConfig.baseUrl) {
baseUrlForTest = EnvConfig.devBaseUrl;
} else {
baseUrlForTest = EnvConfig.baseUrl;
}
baseUrl = baseUrlForTest.toLowerCase();
sl<Dio>().options.baseUrl = baseUrl;
sl<AppConfig>().setBaseUrl(baseUrlForTest.toLowerCase());
sl<Dio>().options.baseUrl = baseUrlForTest.toLowerCase();
controller.clear();
});
},
buttonStyle: const ButtonStyle(),
),
SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10),
if (baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase())
SizedBox(height: baseUrlForTest != sl<AppConfig>().baseUrl ? 40 : 10),
if (sl<AppConfig>().baseUrl.toLowerCase() != EnvConfig.baseUrl.toLowerCase())
ListView.builder(
shrinkWrap: true,
itemCount: testCases.length,

View File

@@ -1,8 +1,9 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
@@ -139,7 +140,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path}',
),
),
if (!state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented)
@@ -211,7 +212,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
height: 52,
width: 52,
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${state.diary.meals[mealIndex].photos[i].path}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.diary.meals[mealIndex].photos[i].path}',
fit: BoxFit.fitWidth,
),
),

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
@@ -354,7 +354,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${dietState.diet.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${dietState.diet.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
@@ -37,7 +38,7 @@ class AuthorCard extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(baseUrl + author.authorProfileIcon),
image: NetworkImage(sl<AppConfig>().baseUrl + author.authorProfileIcon),
fit: BoxFit.cover,
),
),

View File

@@ -1,5 +1,6 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
@@ -35,7 +36,7 @@ class MoreOptions extends StatelessWidget {
onTap: disableActions == true
? null
: () {
Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path'));
Clipboard.setData(ClipboardData(text: '${sl<AppConfig>().baseUrl}/ru/$path'));
showInfoToast(s.link_was_copied);
},
child: Row(
@@ -62,7 +63,7 @@ class MoreOptions extends StatelessWidget {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/$path',),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/$path',),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,7 +1,8 @@
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/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
@@ -64,7 +65,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
authorName: state.feedBlogPostList[widget.index].author.authorName,
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
? state.feedBlogPostList[widget.index].author.authorProfileIcon
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
: sl<AppConfig>().baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
),
);
@@ -75,7 +76,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
foregroundImage: NetworkImage(
state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
? state.feedBlogPostList[widget.index].author.authorProfileIcon
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
: sl<AppConfig>().baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
@@ -157,15 +158,15 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
onTap: () {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(imageUrls: ['$baseUrl/storage/${state.feedBlogPostList[widget.index].image}'], index: 0),
PhotoViewScreen(imageUrls: ['${sl<AppConfig>().baseUrl}/storage/${state.feedBlogPostList[widget.index].image}'], index: 0),
);
},
child: Hero(
tag: '$baseUrl/storage/${state.feedBlogPostList[widget.index].image}',
tag: '${sl<AppConfig>().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}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.feedBlogPostList[widget.index].image}',
errorWidget: (context, str, object) => const SizedBox.shrink(),
),
),
@@ -219,7 +220,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/blog_post/${state.feedBlogPostList[widget.index].id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,5 +1,5 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/comments/presentation/comments_actions_bottom_sheet.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
@@ -99,13 +99,13 @@ class _CommentsWidgetState extends State<CommentsWidget> {
extra: Author(
authorSlug: comment.authorSlug,
authorName: comment.authorName,
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : sl<AppConfig>().baseUrl + comment.authorProfileIcon,
authorIsMe: isMe,
),
);
},
child: CircleAvatar(
foregroundImage: NetworkImage(comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon),
foregroundImage: NetworkImage(comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : sl<AppConfig>().baseUrl + comment.authorProfileIcon),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: radius,
),

View File

@@ -1,5 +1,5 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
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/profile_avatar_cubit/profile_avatar_cubit.dart';
@@ -14,10 +14,7 @@ import 'package:go_router/go_router.dart';
class ConsultantWidget extends StatelessWidget {
final FeedConsultantModel consultant;
const ConsultantWidget({
super.key,
required this.consultant,
});
const ConsultantWidget({super.key, required this.consultant});
@override
Widget build(BuildContext context) {
@@ -36,10 +33,7 @@ class ConsultantWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: const Color(0xFFF6F6F6),
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -51,46 +45,32 @@ class ConsultantWidget extends StatelessWidget {
children: [
Ink(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Text(
'${consultant.ratingPlace}',
style: TextStyle(
fontSize: 20,
color: appTheme.blueA400,
fontWeight: FontWeight.w600,
),
style: TextStyle(fontSize: 20, color: appTheme.blueA400, fontWeight: FontWeight.w600),
),
),
const SizedBox(width: 8),
CircleAvatar(
foregroundImage: NetworkImage(baseUrl + consultant.photo),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
if (consultant.photo.contains('https'))
CircleAvatar(foregroundImage: NetworkImage(consultant.photo), backgroundImage: const AssetImage('assets/images/no_avatar.png'))
else
CircleAvatar(
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + consultant.photo),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
consultant.name,
style: const TextStyle(
height: 1,
fontWeight: FontWeight.w500,
fontSize: 15,
color: Color(0xFF323F4B),
),
style: const TextStyle(height: 1, fontWeight: FontWeight.w500, fontSize: 15, color: Color(0xFF323F4B)),
),
const SizedBox(height: 4),
Text(
'Рейтинг: ${consultant.totalScores}',
style: TextStyle(
color: appTheme.blueA400,
height: 1,
fontWeight: FontWeight.w500,
fontSize: 12,
),
style: TextStyle(color: appTheme.blueA400, height: 1, fontWeight: FontWeight.w500, fontSize: 12),
),
],
),
@@ -98,21 +78,13 @@ class ConsultantWidget extends StatelessWidget {
),
Ink(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.white,
),
child: SvgPicture.asset(
'assets/icons/go_to_arrow.svg',
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), color: Colors.white),
child: SvgPicture.asset('assets/icons/go_to_arrow.svg'),
),
],
),
const SizedBox(height: 8),
Text(
consultant.description,
maxLines: 2,
),
Text(consultant.description, maxLines: 2),
if (consultant.badges.isNotEmpty) const SizedBox(height: 8),
_badges(consultant.badges, context),
],
@@ -128,16 +100,8 @@ class ConsultantWidget extends StatelessWidget {
children: [
for (int i = 0; i < badges.length; i++) ...[
badges[i].image.contains('svg')
? SvgPicture.network(
'$baseUrl/storage/${badges[i].image}',
width: 28,
height: 28,
)
: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${badges[i].image}',
width: 28,
height: 28,
),
? SvgPicture.network('${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28)
: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28),
i < badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
],
],

View File

@@ -1,7 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
@@ -56,7 +57,7 @@ class _DietsWidgetState extends State<DietsWidget> {
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.diet.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.image}',
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
@@ -189,7 +190,7 @@ class _DietsWidgetState extends State<DietsWidget> {
final box = context.findRenderObject() as RenderBox?;
final result = await SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/diet/${widget.diet.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/${widget.diet.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,7 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
@@ -61,7 +62,7 @@ class _PlanWidgetState extends State<PlanWidget> {
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.plan.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.plan.image}',
fit: BoxFit.cover,
width: MediaQuery.of(context).size.width,
height: 426,
@@ -187,7 +188,7 @@ class _PlanWidgetState extends State<PlanWidget> {
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(baseUrl + widget.plan.author.authorProfileIcon),
image: NetworkImage(sl<AppConfig>().baseUrl + widget.plan.author.authorProfileIcon),
fit: BoxFit.cover,
),
),
@@ -263,7 +264,7 @@ class _PlanWidgetState extends State<PlanWidget> {
final box = context.findRenderObject() as RenderBox?;
final result = await SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/diet/${widget.plan.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/${widget.plan.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,8 +1,9 @@
// ignore_for_file: dead_code
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
@@ -75,7 +76,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.recipe.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.recipe.image}',
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
@@ -231,7 +232,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/recipe/${widget.recipe.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/recipe/${widget.recipe.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
@@ -18,7 +19,7 @@ class _GlossaryScreenState extends State<GlossaryScreen> {
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse('$baseUrl/product_nutritional_value'));
..loadRequest(Uri.parse('${sl<AppConfig>().baseUrl}/product_nutritional_value'));
}
@override

View File

@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/token_storage.dart';
@@ -117,7 +118,7 @@ class MenuScreen extends StatelessWidget {
CupertinoDialogAction(
onPressed: () async {
try {
await sl<Dio>().post('$baseUrl/api/profile/logout');
await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout');
} finally {
await TokenStorage.removeTokenInStorage();
if (context.mounted) {

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
@@ -33,7 +34,7 @@ class _BlockedUserWidgetState extends State<BlockedUserWidget> {
child: Row(
children: [
CircleAvatar(
foregroundImage: NetworkImage(baseUrl + widget.profile.profilePhotoUrl),
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + widget.profile.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: 28,
),

View File

@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
@@ -56,7 +57,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.diet.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.diet.image}',
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
@@ -153,7 +154,7 @@ class _MyDietWidgetState extends State<MyDietWidget> {
final box = context.findRenderObject() as RenderBox?;
await SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/diet/${widget.diet.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/diet/${widget.diet.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
@@ -42,7 +43,7 @@ class _MyPlanWidgetState extends State<MyPlanWidget> {
onTap: () => GoRouter.of(context).pushNamed('aboutPlanScreen', extra: {'slug': widget.plan.slug, 'navigateToComments': false}),
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(imageUrl: '$baseUrl/storage/${widget.plan.image}', fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width),
child: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.plan.image}', fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width),
),
),
Positioned(
@@ -157,7 +158,7 @@ class _MyPlanWidgetState extends State<MyPlanWidget> {
final box = context.findRenderObject() as RenderBox?;
await SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/plan/${widget.plan.id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/plan/${widget.plan.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
@@ -31,7 +32,7 @@ class PlanInfo extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/$imagePath',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/$imagePath',
height: 220,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
@@ -401,7 +401,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${state.planModel?.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.planModel?.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
@@ -655,7 +656,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
borderRadius: BorderRadius.circular(4),
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
),
child: SvgPicture.network('$baseUrl/storage/${state.profile.badges[i].image}', width: 40, height: 40),
child: SvgPicture.network('${sl<AppConfig>().baseUrl}/storage/${state.profile.badges[i].image}', width: 40, height: 40),
)
: Tooltip(
message: state.profile.badges[i].description,
@@ -667,7 +668,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
borderRadius: BorderRadius.circular(4),
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
),
child: CachedNetworkImage(imageUrl: '$baseUrl/storage/${state.profile.badges[i].image}', width: 40, height: 40),
child: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.profile.badges[i].image}', width: 40, height: 40),
),
i < state.profile.badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
],
@@ -803,7 +804,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
icon: 'assets/icons/eye.svg',
text: 'Посмотреть',
onTap: () async {
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [baseUrl + state.profile.profilePhotoUrl], index: 0));
context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [sl<AppConfig>().baseUrl + state.profile.profilePhotoUrl], index: 0));
},
),
if (widget.author == null || widget.author!.authorIsMe) ...[
@@ -885,7 +886,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
child: CircleAvatar(
radius: 32,
foregroundImage: NetworkImage(
state.profile.profilePhotoUrl.contains('https') ? state.profile.profilePhotoUrl : baseUrl + state.profile.profilePhotoUrl,
state.profile.profilePhotoUrl.contains('https') ? state.profile.profilePhotoUrl : sl<AppConfig>().baseUrl + state.profile.profilePhotoUrl,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
@@ -960,7 +961,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
onTap: () {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(uri: Uri.parse('$baseUrl/ru/profile/${state.profile.slug}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size),
ShareParams(uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/profile/${state.profile.slug}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size),
);
},
),

View File

@@ -1,7 +1,8 @@
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/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
@@ -69,7 +70,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
foregroundImage: NetworkImage(
state.blogPosts[widget.index].author.authorProfileIcon.contains('https')
? state.blogPosts[widget.index].author.authorProfileIcon
: baseUrl + state.blogPosts[widget.index].author.authorProfileIcon,
: sl<AppConfig>().baseUrl + state.blogPosts[widget.index].author.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
),
@@ -157,17 +158,17 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: ['$baseUrl/storage/${state.blogPosts[widget.index].image}'],
imageUrls: ['${sl<AppConfig>().baseUrl}/storage/${state.blogPosts[widget.index].image}'],
index: 0,
),
);
},
child: Hero(
tag: '$baseUrl/storage/${state.blogPosts[widget.index].image}',
tag: '${sl<AppConfig>().baseUrl}/storage/${state.blogPosts[widget.index].image}',
child: CachedNetworkImage(
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
imageUrl: '$baseUrl/storage/${state.blogPosts[widget.index].image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.blogPosts[widget.index].image}',
errorWidget: (context, str, object) => const SizedBox.shrink(),
),
),
@@ -226,7 +227,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
final box = context.findRenderObject() as RenderBox?;
SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/blog_post/${state.blogPosts[widget.index].id}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/blog_post/${state.blogPosts[widget.index].id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/profile/models/sub_model.dart';
import 'package:eatvio_app/features/profile/presentation/widgets/sub_action_button_widget.dart';
@@ -36,7 +37,7 @@ class _SubWidgetState extends State<SubWidget> {
child: Row(
children: [
CircleAvatar(
foregroundImage: NetworkImage(baseUrl + widget.subModel.profilePhotoUrl),
foregroundImage: NetworkImage(sl<AppConfig>().baseUrl + widget.subModel.profilePhotoUrl),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: 28,
),

View File

@@ -2,7 +2,7 @@ import 'dart:developer';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
@@ -247,7 +247,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: '$baseUrl/storage/${recipeState.recipe.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${recipeState.recipe.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,

View File

@@ -1,5 +1,5 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
@@ -71,7 +71,7 @@ class _MyRecipeWidgetState extends State<MyRecipeWidget> {
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: '$baseUrl/storage/${widget.recipe.image}',
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${widget.recipe.image}',
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
@@ -162,7 +162,7 @@ class _MyRecipeWidgetState extends State<MyRecipeWidget> {
final box = context.findRenderObject() as RenderBox?;
await SharePlus.instance.share(
ShareParams(
uri: Uri.parse('$baseUrl/ru/recipe/${widget.recipe.slug}'),
uri: Uri.parse('${sl<AppConfig>().baseUrl}/ru/recipe/${widget.recipe.slug}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
),
);

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
@@ -44,7 +45,7 @@ class StepsWidget extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
'$baseUrl/storage/${recipe.steps![index].image}',
'${sl<AppConfig>().baseUrl}/storage/${recipe.steps![index].image}',
fit: BoxFit.cover,
),
),

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
@@ -89,7 +90,7 @@ class _VerifyScreenState extends State<VerifyScreen> {
},
),
)
..loadRequest(Uri.parse('$baseUrl/login_with_pass_key?id=${widget.passKey}'));
..loadRequest(Uri.parse('${sl<AppConfig>().baseUrl}/login_with_pass_key?id=${widget.passKey}'));
}
@override

View File

@@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:developer';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
@@ -54,7 +54,7 @@ class _WebViewRegistrationScreenState extends State<WebViewRegistrationScreen> {
},
),
)
..loadRequest(Uri.parse('$baseUrl/registration/step1'));
..loadRequest(Uri.parse('${sl<AppConfig>().baseUrl}/registration/step1'));
_injectJavaScript();
}

View File

@@ -1,4 +1,6 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/generated/l10n.dart';
@@ -71,7 +73,7 @@ class SupportScreen extends StatelessWidget {
const SizedBox(height: 12),
if (!hidePlans) ...[
InkWell(
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/public_offer.pdf')),
onTap: () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/docs/public_offer.pdf')),
borderRadius: BorderRadius.circular(16),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
@@ -81,7 +83,7 @@ class SupportScreen extends StatelessWidget {
const SizedBox(height: 8),
],
InkWell(
onTap: () async => await launchUrl(Uri.parse('$baseUrl/docs/privacy.pdf')),
onTap: () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/docs/privacy.pdf')),
borderRadius: BorderRadius.circular(16),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
@@ -90,7 +92,7 @@ class SupportScreen extends StatelessWidget {
),
const SizedBox(height: 8),
InkWell(
onTap: () async => await launchUrl(Uri.parse('$baseUrl/company-details')),
onTap: () async => await launchUrl(Uri.parse('${sl<AppConfig>().baseUrl}/company-details')),
borderRadius: BorderRadius.circular(16),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/app_keys.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:flutter/material.dart';
@@ -79,6 +80,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
),
const SizedBox(height: 24),
InkWell(
key: AppKeys.welcomeScreenButton,
onTap: () async {
final sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.setBool('welcomeScreenWasShowed', true);

View File

@@ -28,9 +28,10 @@ class S {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> load(Locale locale) {
final name = (locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final name =
(locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/app_router.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';
@@ -8,7 +9,7 @@ import 'package:flutter_portal/flutter_portal.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
Future<void> main() async {
await AppInitializer.initialize();
await AppInitializer.initialize(buildType: AppBuildType.prod);
await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
}
@@ -18,15 +19,15 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: AppConfig.globalBlocProviders,
providers: sl<AppConfig>().globalBlocProviders,
child: Portal(
child: KeyboardDismisser(
child: MaterialApp.router(
theme: ThemeHelper().themeData(),
routerConfig: AppRouter.appRouter,
routerConfig: AppRouter.appRouter,
debugShowCheckedModeBanner: false,
localizationsDelegates: AppConfig.localizationDelegates,
supportedLocales: AppConfig.supportedLocales,
localizationsDelegates: sl<AppConfig>().localizationDelegates,
supportedLocales: sl<AppConfig>().supportedLocales,
),
),
),

View File

@@ -88,6 +88,8 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^6.0.0
integration_test:
sdk: flutter
flutter:
uses-material-design: true