diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 08e122ba..03dec547 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -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 = 33;
+ CURRENT_PROJECT_VERSION = 35;
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 = 33;
+ CURRENT_PROJECT_VERSION = 35;
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 = 33;
+ CURRENT_PROJECT_VERSION = 35;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 219c1ec3..0491da0c 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 33
+ 35
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/lib/core/image_compressing/image_compress_utility.dart b/lib/core/image_compressing/image_compress_utility.dart
index 6eb66f4e..e3c5fa65 100644
--- a/lib/core/image_compressing/image_compress_utility.dart
+++ b/lib/core/image_compressing/image_compress_utility.dart
@@ -7,11 +7,25 @@ abstract class ImageCompressUtility {
static Future> compressFilesList(List files) async {
final List 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 compressFile(File file) async {
final image = img.decodeImage(file.readAsBytesSync())!;
int width = image.width;
diff --git a/lib/core/utils/date_time_utils.dart b/lib/core/utils/date_time_utils.dart
index f10ae9ec..bcc82aeb 100644
--- a/lib/core/utils/date_time_utils.dart
+++ b/lib/core/utils/date_time_utils.dart
@@ -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');
}
}
diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart
index e2e222cd..f8b43e4f 100644
--- a/lib/features/chat/presentation/pages/chat_screen.dart
+++ b/lib/features/chat/presentation/pages/chat_screen.dart
@@ -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 {
final List<({int id, int index})> unreadMessagesIdsCopy = [];
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
late AppLifecycleListener _listener;
+ List pickedFiles = [];
void _onHide() {
chatRoomCubit.cancelTimer();
@@ -336,73 +339,155 @@ class _ChatScreenState extends State {
),
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.removeAt(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 {
);
}
+ 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 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(
context: context,
@@ -435,9 +570,14 @@ class _ChatScreenState extends State {
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 {
final picker = ImagePicker();
List images = await picker.pickMultiImage();
if (images != []) {
- List 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 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 {
],
);
if (result != null) {
- List files = result.paths.map((path) => File(path!)).toList();
- await chatRoomCubit.sendMessage(files: files, userSlug: slug);
+ // List 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(
diff --git a/lib/features/chat/presentation/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
index 1338c60b..44cc67f9 100644
--- a/lib/features/chat/presentation/widgets/chat_entry_widget.dart
+++ b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
@@ -20,15 +20,14 @@ class ChatEntryWidget extends StatefulWidget {
class _ChatEntryWidgetState extends State {
@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 {
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 {
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,
),
),
diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart
index e249c708..54924fcd 100644
--- a/lib/features/chat/repositories/chat_repository.dart
+++ b/lib/features/chat/repositories/chat_repository.dart
@@ -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;
}
}
diff --git a/lib/features/menu/presentation/repositories/blocked_users_repository.dart b/lib/features/menu/presentation/repositories/blocked_users_repository.dart
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/features/menu/services/blocked_users_service.dart b/lib/features/menu/services/blocked_users_service.dart
new file mode 100644
index 00000000..590d979f
--- /dev/null
+++ b/lib/features/menu/services/blocked_users_service.dart
@@ -0,0 +1,24 @@
+import 'package:dio/dio.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 getBlockedUsers();
+
+ @POST('/api/blocked_users/block')
+ Future blockUser({
+ String? slug,
+ String? id,
+ });
+
+ @POST('/api/blocked_users/unblock')
+ Future unblockUser({
+ String? slug,
+ String? id,
+ });
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 8ba374ec..e91dc215 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
-version: 1.1.10+40
+version: 1.1.12+42
publish_to: 'none'
environment: