forked from eatvio/eatvio_app
Merge branch 'main' into deepLinks
This commit is contained in:
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
||||
on: [ pull_request ]
|
||||
|
||||
env:
|
||||
flutter_version: "3.32.0"
|
||||
flutter_version: "3.32.5"
|
||||
java_version: "21.x"
|
||||
flutter_channel: "stable"
|
||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||
|
||||
@@ -16,7 +16,7 @@ if (keystorePropertiesFile.exists()) {
|
||||
android {
|
||||
namespace = "com.eatvioapp.app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
29
assets/icons/blocked_users.svg
Normal file
29
assets/icons/blocked_users.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="19.633818mm"
|
||||
height="19.633816mm"
|
||||
viewBox="0 0 19.633818 19.633816"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-64.066064,-72.435122)">
|
||||
<g
|
||||
id="g2">
|
||||
<path
|
||||
id="path1"
|
||||
style="fill-opacity:0;stroke:#2f80ed;stroke-width:1.065"
|
||||
d="m 83.167381,82.252022 a 9.2844086,9.2844086 0 0 1 -9.284408,9.284408 9.2844086,9.2844086 0 0 1 -9.284409,-9.284408 9.2844086,9.2844086 0 0 1 9.284409,-9.284409 9.2844086,9.2844086 0 0 1 9.284408,9.284409 z" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#2f80ed;stroke-width:1.065;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 68.517124,89.22945 79.520546,75.54452"
|
||||
id="path2" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 986 B |
@@ -388,7 +388,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
CURRENT_PROJECT_VERSION = 37;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -538,7 +538,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
CURRENT_PROJECT_VERSION = 37;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -576,7 +576,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
CURRENT_PROJECT_VERSION = 37;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>32</string>
|
||||
<string>37</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -25,6 +25,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa
|
||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||
@@ -193,6 +194,11 @@ class AppRouter {
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
name: 'blockedUsersScreen',
|
||||
path: '/blocked-users-screen',
|
||||
builder: (context, state) => const BlockedUsersScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/welcome-screen',
|
||||
name: 'welcomeScreen',
|
||||
|
||||
@@ -7,11 +7,25 @@ abstract class ImageCompressUtility {
|
||||
static Future<List<File>> compressFilesList(List<File> files) async {
|
||||
final List<File> compressedFilesList = [];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
compressedFilesList.add(await compressFile(files[i]));
|
||||
if (_isImageFile(files[i].path)) {
|
||||
compressedFilesList.add(await compressFile(files[i]));
|
||||
} else {
|
||||
compressedFilesList.add(files[i]);
|
||||
}
|
||||
}
|
||||
return compressedFilesList;
|
||||
}
|
||||
|
||||
static bool _isImageFile(String path) {
|
||||
final lower = path.toLowerCase();
|
||||
return lower.endsWith('.png') ||
|
||||
lower.endsWith('.jpg') ||
|
||||
lower.endsWith('.jpeg') ||
|
||||
lower.endsWith('.gif') ||
|
||||
lower.endsWith('.bmp') ||
|
||||
lower.endsWith('.webp');
|
||||
}
|
||||
|
||||
static Future<File> compressFile(File file) async {
|
||||
final image = img.decodeImage(file.readAsBytesSync())!;
|
||||
int width = image.width;
|
||||
|
||||
@@ -37,6 +37,9 @@ import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||
@@ -164,6 +167,7 @@ Future<void> init() async {
|
||||
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||
|
||||
// Repository
|
||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||
@@ -193,6 +197,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
||||
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
||||
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||
|
||||
// Services
|
||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||
@@ -224,6 +229,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
|
||||
@@ -33,7 +33,7 @@ extension DateTimeExtension on DateTime {
|
||||
}
|
||||
}
|
||||
|
||||
String calculateDifference(DateTime dateTime) {
|
||||
String calculateDifference(DateTime dateTime, [String? format]) {
|
||||
final localDateTime = dateTime.isUtc ? dateTime.toLocal() : dateTime;
|
||||
|
||||
if (localDateTime.isToday()) {
|
||||
@@ -41,6 +41,6 @@ String calculateDifference(DateTime dateTime) {
|
||||
} else if (localDateTime.isYesterday()) {
|
||||
return 'Вчера';
|
||||
} else {
|
||||
return localDateTime.format('d MMMM', 'ru');
|
||||
return localDateTime.format(format ?? 'd MMMM', 'ru');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.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';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
|
||||
class WsManager {
|
||||
final WsService wsService;
|
||||
@@ -71,6 +74,11 @@ class WsManager {
|
||||
}
|
||||
});
|
||||
client?.eventStream.listen((event) {
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
const Duration(milliseconds: 300),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
);
|
||||
log(event.rootObject.toString());
|
||||
});
|
||||
_subscribe();
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -28,6 +29,7 @@ class CustomPopup extends StatelessWidget {
|
||||
final FeedBlogPostModel? feedBlogPostModel;
|
||||
final Recipe? recipe;
|
||||
final Diet? diet;
|
||||
final String userSlug;
|
||||
|
||||
const CustomPopup({
|
||||
super.key,
|
||||
@@ -39,6 +41,7 @@ class CustomPopup extends StatelessWidget {
|
||||
this.feedBlogPostModel,
|
||||
this.recipe,
|
||||
this.diet,
|
||||
required this.userSlug
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -164,6 +167,13 @@ class CustomPopup extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
});
|
||||
addItem(
|
||||
'assets/icons/complain.svg',
|
||||
'Заблокировать автора',
|
||||
() {
|
||||
sl<BlockedUsersCubit>().blockUser(userSlug);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (entries.isNotEmpty && entries.last is PopupMenuItem) {
|
||||
|
||||
@@ -195,7 +195,8 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
path: 'blog_post/${(widget.id)}',
|
||||
id: widget.id,
|
||||
feedBlogPostModel: blogPostState.blogPostModel,
|
||||
),
|
||||
userSlug: blogPostState.blogPostModel?.author.authorSlug ?? '',
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -7,11 +7,13 @@ import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||
@@ -167,6 +169,11 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
List<ChatMessageModel> messagesList = [message, ...(state as ChatRoomStateLoaded).chatMessages];
|
||||
chatRepository.emitNewMessageToChats(message);
|
||||
emit((state as ChatRoomStateLoaded).copyWith(chatMessages: messagesList, isLoading: isLoading));
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
const Duration(milliseconds: 300),
|
||||
() => sl<ChatsCubit>().getChatRooms(),
|
||||
);
|
||||
});
|
||||
|
||||
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
||||
@@ -184,7 +191,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
try {
|
||||
await chatRepository.sendMessage(
|
||||
data: data,
|
||||
files: files != null ? await ImageCompressUtility.compressFilesList(files) : null,
|
||||
files: files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null,
|
||||
slug: userSlug,
|
||||
);
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
@@ -8,6 +9,7 @@ import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -47,6 +49,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
||||
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
|
||||
late AppLifecycleListener _listener;
|
||||
List<File> pickedFiles = [];
|
||||
|
||||
void _onHide() {
|
||||
chatRoomCubit.cancelTimer();
|
||||
@@ -336,73 +339,155 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () => showImagePicker(state.chatRoom.users.receiver.slug),
|
||||
customBorder: const CircleBorder(),
|
||||
child: Ink(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
for (int i = 0; i < pickedFiles.length; i++) ...[
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_isImageFile(pickedFiles[i].path)
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
final imageFiles = pickedFiles.where((f) => _isImageFile(f.path)).toList();
|
||||
final imageIndex = _imageIndexAt(pickedFiles, i);
|
||||
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: imageFiles.map((e) => e.path).toList(),
|
||||
isLocal: true,
|
||||
index: imageIndex,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: pickedFiles[i].path,
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
height: 104,
|
||||
width: 104,
|
||||
child: Image.file(
|
||||
pickedFiles[i],
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: _buildFilePreview(pickedFiles[i]),
|
||||
Positioned(
|
||||
top: -8,
|
||||
right: -8,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
pickedFiles.remove(pickedFiles[i]);
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.gray50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
size: 20,
|
||||
Icons.close,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextFormField(
|
||||
expands: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: textEditingController,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () => showImagePicker(state.chatRoom.users.receiver.slug),
|
||||
customBorder: const CircleBorder(),
|
||||
child: Ink(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextFormField(
|
||||
expands: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: textEditingController,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
|
||||
chatRoomCubit.sendMessage(
|
||||
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
|
||||
files: pickedFiles.isEmpty ? null : pickedFiles,
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
pickedFiles.clear();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
if (textEditingController.text.isNotEmpty) {
|
||||
chatRoomCubit.sendMessage(
|
||||
data: textEditingController.text.trim(),
|
||||
userSlug: state.chatRoom.users.receiver.slug,
|
||||
);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -425,6 +510,56 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilePreview(File file) {
|
||||
final fileName = file.path.split('/').last;
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
height: 104,
|
||||
width: 104,
|
||||
color: const Color(0xFFE0E0E0), // Светло-серый фон
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
fileName,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isImageFile(String path) {
|
||||
final lower = path.toLowerCase();
|
||||
return lower.endsWith('.png') ||
|
||||
lower.endsWith('.jpg') ||
|
||||
lower.endsWith('.jpeg') ||
|
||||
lower.endsWith('.gif') ||
|
||||
lower.endsWith('.bmp') ||
|
||||
lower.endsWith('.webp');
|
||||
}
|
||||
|
||||
int _imageIndexAt(List<File> allFiles, int targetIndex) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < allFiles.length; i++) {
|
||||
if (_isImageFile(allFiles[i].path)) {
|
||||
if (i == targetIndex) return count;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
showImagePicker(String slug) {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
@@ -435,9 +570,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
Navigator.of(context).pop();
|
||||
final picker = ImagePicker();
|
||||
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||
if (image != null) {
|
||||
await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||
}
|
||||
setState(() {
|
||||
if (image != null) {
|
||||
pickedFiles.add(File(image.path));
|
||||
}
|
||||
});
|
||||
// if (image != null) {
|
||||
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||
// }
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
@@ -453,8 +593,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final picker = ImagePicker();
|
||||
List<XFile> images = await picker.pickMultiImage();
|
||||
if (images != []) {
|
||||
List<File> files = images.map((image) => File(image.path)).toList();
|
||||
await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
setState(() {
|
||||
pickedFiles.addAll(images.map((image) => File(image.path)).toList());
|
||||
});
|
||||
// List<File> files = images.map((image) => File(image.path)).toList();
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
@@ -493,8 +636,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
],
|
||||
);
|
||||
if (result != null) {
|
||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||
await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
// List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||
setState(() {
|
||||
pickedFiles.addAll(result.paths.map((path) => File(path!)).toList());
|
||||
});
|
||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||
}
|
||||
},
|
||||
child: const Row(
|
||||
|
||||
@@ -67,11 +67,16 @@ class _ChatsScreenState extends State<ChatsScreen> {
|
||||
),
|
||||
];
|
||||
},
|
||||
body: switch (state.state) {
|
||||
BaseState.error => DataErrorWidget(onReload: () => context.read<ChatsCubit>().getChatRooms()),
|
||||
BaseState.success => _buildChatList(context, state),
|
||||
_ => const Loader(),
|
||||
},
|
||||
body: RefreshIndicator(
|
||||
child: switch (state.state) {
|
||||
BaseState.error => DataErrorWidget(onReload: () => context.read<ChatsCubit>().getChatRooms()),
|
||||
BaseState.success => _buildChatList(context, state),
|
||||
_ => const Loader(),
|
||||
},
|
||||
onRefresh: () async {
|
||||
await context.read<ChatsCubit>().getChatRooms();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -20,15 +20,14 @@ class ChatEntryWidget extends StatefulWidget {
|
||||
class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
return GestureDetector(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: {
|
||||
'chatId': widget.chatRoom.id.toString(),
|
||||
},
|
||||
),
|
||||
child: Ink(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -61,7 +60,13 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||
child: Text(
|
||||
widget.chatRoom.lastMessage!.data.toString().trim(),
|
||||
widget.chatRoom.lastMessage!.mediaData != null
|
||||
? widget.chatRoom.lastMessage!.isCurrentUserAuthor == true
|
||||
? 'Вы: 📎 Вложение'
|
||||
: '📎 Вложение'
|
||||
: widget.chatRoom.lastMessage!.isCurrentUserAuthor == true
|
||||
? 'Вы: ${widget.chatRoom.lastMessage!.data.toString().trim()}'
|
||||
: widget.chatRoom.lastMessage!.data.toString().trim(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: CustomTextStyles.bodyMediumBluegray800,
|
||||
@@ -77,7 +82,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
widget.chatRoom.lastMessage!.updatedAt.format('dd MMM', 'ru'),
|
||||
calculateDifference(widget.chatRoom.lastMessage!.updatedAt, 'dd MMM'),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
@@ -51,7 +52,8 @@ class ChatRepository {
|
||||
data: data,
|
||||
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
||||
);
|
||||
} catch (e) {
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
isAuthor: widget.diet.author.authorIsMe,
|
||||
path: 'diet/${widget.diet.slug}',
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
userSlug: widget.diet.author.authorSlug,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
|
||||
@@ -51,6 +51,29 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
GetPlansComments(),
|
||||
SendPlansComment(),
|
||||
GetRecipesComments(),
|
||||
AuthServiceTest(),
|
||||
DiaryServiceTest(),
|
||||
BlockedUserServiceTest(),
|
||||
PlanServiceTest(),
|
||||
AboutRecipeServiceTest(),
|
||||
BlogPostServiceTest(),
|
||||
CalendarServiceTest(),
|
||||
CardsServiceTest(),
|
||||
ConsultantServiceTest(),
|
||||
ConsultationServiceTest(),
|
||||
CreateDietServiceTest(),
|
||||
CreatePlanServiceTest(),
|
||||
GlossaryServiceTest(),
|
||||
MyDietsServiceTest(),
|
||||
MyPlansServiceTest(),
|
||||
MyRecipesServiceTest(),
|
||||
NotificationsServiceTest(),
|
||||
PlansServiceTest(),
|
||||
ProfileServiceTest(),
|
||||
RegistrationServiceTest(),
|
||||
ResetPasswordServiceTest(),
|
||||
SettingsServiceTest(),
|
||||
WsServiceTest(),
|
||||
];
|
||||
final controller = TextEditingController();
|
||||
late final List<TestState> testsStates;
|
||||
@@ -132,17 +155,56 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
||||
setState(() {
|
||||
testsStates[index] = TestState.loading;
|
||||
});
|
||||
final result = await testCases[index].run(baseUrl: baseUrlForTest);
|
||||
if (result) {
|
||||
setState(() {
|
||||
testsStates[index] = TestState.success;
|
||||
});
|
||||
showSuccessToast('${testCases[index].name} passed');
|
||||
} else {
|
||||
try {
|
||||
final result = await testCases[index].run(baseUrl: baseUrlForTest);
|
||||
if (result.success) {
|
||||
setState(() {
|
||||
testsStates[index] = TestState.success;
|
||||
});
|
||||
showSuccessToast('${testCases[index].name} passed');
|
||||
} else {
|
||||
setState(() {
|
||||
testsStates[index] = TestState.fail;
|
||||
});
|
||||
showErrorToast('${testCases[index].name} failed');
|
||||
// Show AlertDialog with error description
|
||||
if (mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Test Failed'),
|
||||
content: SingleChildScrollView(child: Text(result.error ?? 'No error details.')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e, stack) {
|
||||
setState(() {
|
||||
testsStates[index] = TestState.fail;
|
||||
});
|
||||
showErrorToast('${testCases[index].name} failed');
|
||||
// Show AlertDialog with error description
|
||||
if (mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Test Failed'),
|
||||
content: SingleChildScrollView(child: Text(e.toString() + '\n' + stack.toString())),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
buttonStyle: const ButtonStyle(),
|
||||
|
||||
@@ -4,19 +4,48 @@ import 'dart:developer';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
|
||||
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
||||
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
||||
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||
|
||||
class TestResult {
|
||||
final bool success;
|
||||
final String? error;
|
||||
TestResult(this.success, [this.error]);
|
||||
}
|
||||
|
||||
sealed class TestCases {
|
||||
String get name => throw UnimplementedError();
|
||||
|
||||
Future<bool> run({required String baseUrl}) async => throw UnimplementedError();
|
||||
Future<TestResult> run({required String baseUrl}) async => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class CreateRecipeTask implements TestCases {
|
||||
@@ -24,7 +53,7 @@ class CreateRecipeTask implements TestCases {
|
||||
String get name => 'api/recipes/create';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl));
|
||||
@@ -41,10 +70,16 @@ class CreateRecipeTask implements TestCases {
|
||||
steps: [Step(description: 'desc')],
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
} on DioError catch (e) {
|
||||
final res = e.response;
|
||||
if (res != null && (res.statusCode == null || res.statusCode! < 200 || res.statusCode! >= 300)) {
|
||||
return TestResult(false, 'DioError: statusCode=${res.statusCode}\n${res.data}');
|
||||
}
|
||||
return TestResult(false, e.toString());
|
||||
} catch (e, stack) {
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
return true;
|
||||
return TestResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,82 +88,66 @@ class Test850task implements TestCases {
|
||||
String get name => 'task #850';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
// get all of needed services
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final profileService = ProfileService(dio, baseUrl: baseUrl);
|
||||
final dietsService = DietService(dio, baseUrl: baseUrl);
|
||||
|
||||
// get all of needed data
|
||||
final dietsPaginationModel = await feedService.getDiets(page: 1);
|
||||
final chosenDietsPaginationModel = await profileService.getChosenDiets();
|
||||
final lastChosenDiet = chosenDietsPaginationModel.diets.firstOrNull;
|
||||
final today = DateTime.now();
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
final tomorrow = today.add(const Duration(days: 1));
|
||||
|
||||
// run test case
|
||||
for (final diet in dietsPaginationModel.data) {
|
||||
if (!diet.author.authorIsMe) {
|
||||
if (lastChosenDiet?.state == 'started' || lastChosenDiet?.state == 'inited') {
|
||||
try {
|
||||
await dietsService.cancelDiet(id: lastChosenDiet?.dietId ?? 0);
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
log('unexpected error7: $e');
|
||||
return false;
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
}
|
||||
try {
|
||||
await dietsService.startDiet(id: diet.id, body: {'date': yesterday.format('yyyy-MM-dd')});
|
||||
|
||||
return false;
|
||||
return TestResult(false, 'Expected error but got success');
|
||||
} catch (e) {
|
||||
log('expect ot have error: $e');
|
||||
}
|
||||
|
||||
// test start diet with correct date
|
||||
try {
|
||||
await dietsService.startDiet(id: diet.id, body: {'date': today.format('yyyy-MM-dd')});
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
log('unexpected error2: $e');
|
||||
return false;
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
|
||||
// test cancelation of diet
|
||||
try {
|
||||
await dietsService.cancelDiet(id: diet.id);
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
log('unexpected error3: $e');
|
||||
return false;
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
|
||||
// test start diet with correct date
|
||||
try {
|
||||
await dietsService.startDiet(id: diet.id, body: {'date': tomorrow.format('yyyy-MM-dd')});
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
log('unexpected error4: $e');
|
||||
return false;
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
|
||||
// test cancelation of diet
|
||||
try {
|
||||
await dietsService.cancelDiet(id: diet.id);
|
||||
} catch (e) {
|
||||
} catch (e, stack) {
|
||||
log('unexpected error5: $e');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return TestResult(true);
|
||||
} catch (e, stack) {
|
||||
log('unexpected error6: $e');
|
||||
return false;
|
||||
return TestResult(false, e.toString() + '\n' + stack.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,16 +158,16 @@ class GetChatRoomPreviews implements TestCases {
|
||||
String get name => '/api/chat/index';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
// get all of needed services
|
||||
// final dio = sl<Dio>();
|
||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||
|
||||
// await chatService.getChatRooms();
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,16 +177,16 @@ class GetChatRoom implements TestCases {
|
||||
String get name => '/api/chat/show/{id}';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
// final dio = sl<Dio>();
|
||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||
|
||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||
// await chatService.getChatRoom(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '');
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,16 +196,16 @@ class SendMessage implements TestCases {
|
||||
String get name => '/api/chat/{id}/message/send';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
// final dio = sl<Dio>();
|
||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||
|
||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||
// await chatService.sendMessage(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', data: 'test message');
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,16 +215,16 @@ class GetChatMessages implements TestCases {
|
||||
String get name => '/api/chat/show/{id}/messages?page={page}';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
// final dio = sl<Dio>();
|
||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||
|
||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||
// await chatService.getChatMessages(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', page: 1);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +234,7 @@ class GetChatRoomIdByUserSlug implements TestCases {
|
||||
String get name => '/api/chat/get_by_user/{user}';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||
@@ -230,9 +249,9 @@ class GetChatRoomIdByUserSlug implements TestCases {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,14 +262,14 @@ class GetBlogPostList implements TestCases {
|
||||
String get name => '/api/blogPosts/index';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
await feedService.getBlogPostList(page: 1);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +279,7 @@ class GetRecipes implements TestCases {
|
||||
String get name => '/api/recipes/index';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
@@ -269,9 +288,9 @@ class GetRecipes implements TestCases {
|
||||
final tags = tagsModel.data;
|
||||
final tagsIds = tags.map((e) => e.id).toList();
|
||||
await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,14 +300,14 @@ class GetDiets implements TestCases {
|
||||
String get name => '/api/diets/index';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
await feedService.getDiets(page: 1, query: null);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,14 +317,14 @@ class GetPlans implements TestCases {
|
||||
String get name => '/api/plans/index';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
await feedService.getPlans(page: 1, query: null);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,16 +334,16 @@ class GetBlogPostComments implements TestCases {
|
||||
String get name => '/api/blogPosts/{id}/comments/show';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
||||
final blogPosts = blogPostsModel.feedBlogPostList;
|
||||
await feedService.getBlogPostComments(id: blogPosts.first.id.toString());
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,16 +353,16 @@ class SendBlogPostComment implements TestCases {
|
||||
String get name => '/api/blogPosts/{id}/comment/create';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
||||
final blogPosts = blogPostsModel.feedBlogPostList;
|
||||
await feedService.sendBlogPostComment(id: blogPosts.first.id.toString(), body: 'auto test message');
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,16 +372,16 @@ class GetDietsComments implements TestCases {
|
||||
String get name => '/api/diets/{id}/comments/show';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
||||
final diets = dietsModel.data;
|
||||
await feedService.getDietsComments(id: diets.first.id.toString());
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,16 +391,16 @@ class SendDietsComment implements TestCases {
|
||||
String get name => '/api/diets/{id}/comment/create';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
||||
final diets = dietsModel.data;
|
||||
await feedService.sendDietsComment(id: diets.first.id.toString(), body: 'auto test message');
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,16 +410,16 @@ class GetPlansComments implements TestCases {
|
||||
String get name => '/api/plans/{slug}/comments/show';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final plansModel = await feedService.getPlans(page: 1, query: null);
|
||||
final plans = plansModel.data;
|
||||
await feedService.getPlansComments(slug: plans.first.slug);
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,16 +429,16 @@ class SendPlansComment implements TestCases {
|
||||
String get name => '/api/plans/{slug}/comment/create';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
final plansModel = await feedService.getPlans(page: 1, query: null);
|
||||
final plans = plansModel.data;
|
||||
await feedService.sendPlansComment(slug: plans.first.slug, body: 'auto test message');
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,7 +448,7 @@ class GetRecipesComments implements TestCases {
|
||||
String get name => '/api/recipes/{id}/comments/show';
|
||||
|
||||
@override
|
||||
Future<bool> run({required String baseUrl}) async {
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||
@@ -440,9 +459,426 @@ class GetRecipesComments implements TestCases {
|
||||
final recipesModel = await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
||||
final recipes = recipesModel.recipesList;
|
||||
await feedService.getRecipesComments(id: recipes.first.id.toString());
|
||||
return true;
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AuthService
|
||||
class AuthServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'AuthService: getTokenByCredentials';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final authService = AuthService(dio, baseUrl: baseUrl);
|
||||
// Use dummy credentials, expecting failure but no crash
|
||||
await authService.getTokenByCredentials(credentials: const CredentialsModel(email: 'reshetnick.mike+user1@gmail.com', password: '1234567890'));
|
||||
return TestResult(false, 'Should not succeed');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy credentials');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DiaryService
|
||||
class DiaryServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'DiaryService: getDiaryIndexByDate';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final diaryService = DiaryService(dio, baseUrl: baseUrl);
|
||||
await diaryService.getDiaryIndexByDate(date: '2023-01-01');
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlockedUserService
|
||||
class BlockedUserServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'BlockedUserService: getBlockedUsers';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final blockedUserService = BlockedUserService(dio, baseUrl: baseUrl);
|
||||
await blockedUserService.getBlockedUsers();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PlanService
|
||||
class PlanServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'PlanService: getPlan';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final planService = PlanService(dio, baseUrl: baseUrl);
|
||||
// Use a dummy slug, expecting failure but no crash
|
||||
await planService.getPlan('dummy-slug');
|
||||
return TestResult(false, 'Should not succeed');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy slug');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AboutRecipeService
|
||||
class AboutRecipeServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'AboutRecipeService: getRecipe';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final aboutRecipeService = AboutRecipeService(dio, baseUrl: baseUrl);
|
||||
await aboutRecipeService.getRecipe('dummy-id');
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlogPostService
|
||||
class BlogPostServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'BlogPostService: createBlogPost';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final blogPostService = BlogPostService(dio, baseUrl: baseUrl);
|
||||
// Use empty FormData, expecting failure but no crash
|
||||
await blogPostService.createBlogPost(formData: FormData());
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for empty FormData');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CalendarService
|
||||
class CalendarServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'CalendarService: getCalendar';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final calendarService = CalendarService(dio, baseUrl: baseUrl);
|
||||
await calendarService.getCalendar();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CardsService
|
||||
class CardsServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'CardsService: getCards';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final cardsService = CardsService(dio, baseUrl: baseUrl);
|
||||
await cardsService.getCards();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConsultantService
|
||||
class ConsultantServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'ConsultantService: getMyRequestStatus';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final consultantService = ConsultantService(dio, baseUrl: baseUrl);
|
||||
await consultantService.getMyRequestStatus(slug: 'dummy-slug');
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy slug');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConsultationService
|
||||
class ConsultationServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'ConsultationService: getConsultations';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final consultationService = ConsultationService(dio);
|
||||
await consultationService.getConsultations();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDietService
|
||||
class CreateDietServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'CreateDietService: getDietPortionsNutrition';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final createDietService = CreateDietService(dio, baseUrl: baseUrl);
|
||||
await createDietService.getDietPortionsNutrition(diet: 0);
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for diet: 0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePlanService
|
||||
class CreatePlanServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'CreatePlanService: createPlan';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final createPlanService = CreatePlanService(dio, baseUrl: baseUrl);
|
||||
await createPlanService.createPlan(formData: FormData());
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for empty FormData');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GlossaryService
|
||||
class GlossaryServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'GlossaryService: getGlossaryByQuery';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final glossaryService = GlossaryService(dio, baseUrl: baseUrl);
|
||||
await glossaryService.getGlossaryByQuery(query: 'test');
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MyDietsService
|
||||
class MyDietsServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'MyDietsService: getDiets';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final myDietsService = MyDietsService(dio, baseUrl: baseUrl);
|
||||
await myDietsService.getDiets(page: 1);
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MyPlansService
|
||||
class MyPlansServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'MyPlansService: getPlans';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final myPlansService = MyPlansService(dio, baseUrl: baseUrl);
|
||||
await myPlansService.getPlans(page: 1);
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MyRecipesService
|
||||
class MyRecipesServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'MyRecipesService: getMyRecipes';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final myRecipesService = MyRecipesService(dio, baseUrl: baseUrl);
|
||||
await myRecipesService.getMyRecipes();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NotificationsService
|
||||
class NotificationsServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'NotificationsService: getNotifications';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final notificationsService = NotificationsService(dio);
|
||||
await notificationsService.getNotifications(page: 1);
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PlansService
|
||||
class PlansServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'PlansService: getPlansBySlug';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final plansService = PlansService(dio, baseUrl: baseUrl);
|
||||
await plansService.getPlansBySlug(author: 'dummy-author');
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for dummy author');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProfileService
|
||||
class ProfileServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'ProfileService: getMyProfile';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final profileService = ProfileService(dio, baseUrl: baseUrl);
|
||||
await profileService.getMyProfile();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegistrationService
|
||||
class RegistrationServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'RegistrationService: getLocationsByAddress';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final registrationService = RegistrationService(dio, baseUrl: baseUrl);
|
||||
await registrationService.getLocationsByAddress(query: 'test');
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResetPasswordService
|
||||
class ResetPasswordServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'ResetPasswordService: sendEmailCodeForPasswordChange';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final resetPasswordService = ResetPasswordService(dio, baseUrl: baseUrl);
|
||||
await resetPasswordService.sendEmailCodeForPasswordChange(data: {'email': 'test@example.com'});
|
||||
return TestResult(false, 'Expected failure but got success');
|
||||
} catch (e) {
|
||||
return TestResult(true, 'Exception expected for test@example.com');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SettingsService
|
||||
class SettingsServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'SettingsService: getProfileSettings';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final settingsService = SettingsService(dio, baseUrl: baseUrl);
|
||||
await settingsService.getProfileSettings();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsService
|
||||
class WsServiceTest implements TestCases {
|
||||
@override
|
||||
String get name => 'WsService: getWebsocket';
|
||||
|
||||
@override
|
||||
Future<TestResult> run({required String baseUrl}) async {
|
||||
try {
|
||||
final dio = sl<Dio>();
|
||||
final wsService = WsService(dio, baseUrl: baseUrl);
|
||||
await wsService.getWebsocket();
|
||||
return TestResult(true);
|
||||
} catch (e) {
|
||||
return TestResult(false, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
id: dietState.diet.id,
|
||||
diet: dietState.diet,
|
||||
userSlug: dietState.diet.author.authorSlug,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
|
||||
@@ -130,6 +130,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}',
|
||||
id: state.feedBlogPostList[widget.index].id,
|
||||
feedBlogPostModel: state.feedBlogPostList[widget.index],
|
||||
userSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -94,6 +94,7 @@ class _DietsWidgetState extends State<DietsWidget> {
|
||||
id: widget.diet.id,
|
||||
complainedContentType: ComplainedContent.diet,
|
||||
diet: widget.diet,
|
||||
userSlug: widget.diet.author.authorSlug,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -100,6 +100,7 @@ class _PlanWidgetState extends State<PlanWidget> {
|
||||
id: widget.plan.id,
|
||||
slug: widget.plan.slug,
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
userSlug: widget.plan.author.authorSlug,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -128,6 +128,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
|
||||
slug: widget.recipe.slug,
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
recipe: widget.recipe,
|
||||
userSlug: widget.recipe.author.authorSlug,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
14
lib/features/menu/models/blocked_users_model.dart
Normal file
14
lib/features/menu/models/blocked_users_model.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'blocked_users_model.g.dart';
|
||||
part 'blocked_users_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class BlockedUsersModel with _$BlockedUsersModel {
|
||||
const factory BlockedUsersModel({
|
||||
@JsonKey(name: 'data') required List<ProfileModel> profilesList,
|
||||
}) = _BlockedUsersModel;
|
||||
|
||||
factory BlockedUsersModel.fromJson(Map<String, Object?> json) => _$BlockedUsersModelFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BlockedUsersCubit extends Cubit<BlockedUsersState> {
|
||||
final BlockedUsersRepository blockedUsersRepository;
|
||||
|
||||
BlockedUsersCubit({
|
||||
required this.blockedUsersRepository,
|
||||
}) : super(const BlockedUsersState());
|
||||
|
||||
getBlockedUsers() async {
|
||||
try {
|
||||
emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers()));
|
||||
} catch (e, st) {
|
||||
emit(BlockedUsersState.error(errorMessage: '$e $st'));
|
||||
}
|
||||
}
|
||||
|
||||
blockUser(String slug) async {
|
||||
await blockedUsersRepository.blockUserBySlug(slug);
|
||||
await getBlockedUsers();
|
||||
}
|
||||
|
||||
unblockUser(String slug) async {
|
||||
await blockedUsersRepository.unblockUserBySlug(slug);
|
||||
await getBlockedUsers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'blocked_users_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class BlockedUsersState with _$BlockedUsersState {
|
||||
const factory BlockedUsersState() = _BlockedUsersState;
|
||||
const factory BlockedUsersState.loading() = BlockedUsersStateLoading;
|
||||
const factory BlockedUsersState.loaded({required List<ProfileModel> profilesList}) = BlockedUsersStateLoaded;
|
||||
const factory BlockedUsersState.error({
|
||||
required String errorMessage,
|
||||
}) = BlockedUsersStateError;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/widgets/blocked_user_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BlockedUsersScreen extends StatefulWidget {
|
||||
const BlockedUsersScreen({super.key});
|
||||
|
||||
@override
|
||||
State<BlockedUsersScreen> createState() => _BlockedUsersScreenState();
|
||||
}
|
||||
|
||||
class _BlockedUsersScreenState extends State<BlockedUsersScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<BlockedUsersCubit>(
|
||||
create: (BuildContext context) => sl<BlockedUsersCubit>()..getBlockedUsers(),
|
||||
child: BlocBuilder<BlockedUsersCubit, BlockedUsersState>(
|
||||
builder: (BuildContext context, BlockedUsersState state) {
|
||||
if (state is BlockedUsersStateLoaded) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Чёрный список'),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (context, index) => BlockedUserWidget(profile: state.profilesList[index]),
|
||||
itemCount: state.profilesList.length,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,8 @@ class MenuScreen extends StatelessWidget {
|
||||
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||
_divider(context),
|
||||
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
||||
// _divider(context),
|
||||
// _tile(
|
||||
// leading: ImageConstant.icnDevTools,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
|
||||
class BlockedUsersRepository {
|
||||
final BlockedUserService blockedUserService;
|
||||
|
||||
BlockedUsersRepository({
|
||||
required this.blockedUserService,
|
||||
});
|
||||
|
||||
Future<List<ProfileModel>> getBlockedUsers() async {
|
||||
try {
|
||||
return (await blockedUserService.getBlockedUsers()).profilesList;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
blockUserBySlug(String slug) async {
|
||||
try {
|
||||
return await blockedUserService.blockUser(
|
||||
data: {
|
||||
'slug': slug,
|
||||
},
|
||||
);
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
unblockUserBySlug(String slug) async {
|
||||
try {
|
||||
return await blockedUserService.unblockUser(
|
||||
data: {
|
||||
'slug': slug,
|
||||
},
|
||||
);
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:eatvio_app/core/const.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';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class BlockedUserWidget extends StatefulWidget {
|
||||
final ProfileModel profile;
|
||||
const BlockedUserWidget({super.key, required this.profile});
|
||||
|
||||
@override
|
||||
State<BlockedUserWidget> createState() => _BlockedUserWidgetState();
|
||||
}
|
||||
|
||||
class _BlockedUserWidgetState extends State<BlockedUserWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'profileScreen',
|
||||
extra: Author(
|
||||
authorSlug: widget.profile.slug,
|
||||
authorName: widget.profile.name,
|
||||
authorProfileIcon: widget.profile.profilePhotoUrl,
|
||||
authorIsMe: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
),
|
||||
),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(baseUrl + widget.profile.profilePhotoUrl),
|
||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||
radius: 28,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.profile.name,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
context.read<BlockedUsersCubit>().unblockUser(widget.profile.slug);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F2F6),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Text(
|
||||
'Убрать',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF6D6F71),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
23
lib/features/menu/services/blocked_users_service.dart
Normal file
23
lib/features/menu/services/blocked_users_service.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/menu/models/blocked_users_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'blocked_users_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
abstract class BlockedUserService {
|
||||
factory BlockedUserService(Dio dio, {String baseUrl}) = _BlockedUserService;
|
||||
|
||||
@GET('/api/blocked_users')
|
||||
Future<BlockedUsersModel> getBlockedUsers();
|
||||
|
||||
@POST('/api/blocked_users/block')
|
||||
Future<void> blockUser({
|
||||
@Body() required Map<String, Object?> data,
|
||||
});
|
||||
|
||||
@POST('/api/blocked_users/unblock')
|
||||
Future<void> unblockUser({
|
||||
@Body() required Map<String, Object?> data,
|
||||
});
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
@@ -1175,6 +1176,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
} else if (value == 'block') {
|
||||
sl<BlockedUsersCubit>().blockUser(state.profile.slug);
|
||||
} else {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -1196,6 +1199,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
value: 'complain',
|
||||
child: Text('Пожаловаться'),
|
||||
),
|
||||
if (widget.author != null)
|
||||
const PopupMenuItem(
|
||||
value: 'block',
|
||||
child: Text('Заблокировать'),
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.more_horiz_rounded),
|
||||
),
|
||||
|
||||
@@ -102,6 +102,7 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
||||
path: 'blog_post/${(state.blogPosts[widget.index].id)}',
|
||||
id: state.blogPosts[widget.index].id,
|
||||
feedBlogPostModel: state.blogPosts[widget.index],
|
||||
userSlug: state.blogPosts[widget.index].author.authorSlug,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -228,6 +228,7 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
|
||||
complainedContentType: ComplainedContent.recipe,
|
||||
slug: widget.recipeSlug,
|
||||
recipe: recipeState.recipe,
|
||||
userSlug: recipeState.recipe.author.authorSlug,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: eatvio_app
|
||||
description: A new Flutter project.
|
||||
version: 1.1.11+41
|
||||
version: 1.1.14+44
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
@@ -38,8 +38,8 @@ dependencies:
|
||||
easy_debounce: ^2.0.3
|
||||
badges: ^3.1.2
|
||||
google_fonts: ^6.2.1
|
||||
syncfusion_flutter_charts: ^29.2.5
|
||||
syncfusion_flutter_gauges: ^29.2.5
|
||||
syncfusion_flutter_charts: ^30.1.37
|
||||
syncfusion_flutter_gauges: ^30.1.37
|
||||
table_calendar: ^3.2.0
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
share_plus: ^11.0.0
|
||||
@@ -83,7 +83,7 @@ dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
Reference in New Issue
Block a user