forked from eatvio/eatvio_app
feat(chat): add plan order message widget and enhance chat bubble to support message types
This commit is contained in:
@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
@@ -24,7 +25,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
required this.isRead,
|
||||
required this.messageState,
|
||||
required this.messageUuid,
|
||||
required this.userSlug
|
||||
required this.userSlug,
|
||||
this.messageType,
|
||||
this.isConsultant,
|
||||
});
|
||||
|
||||
final DateTime sendTime;
|
||||
@@ -35,6 +38,8 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
final MessageState messageState;
|
||||
final String messageUuid;
|
||||
final String userSlug;
|
||||
final String? messageType;
|
||||
final bool? isConsultant;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
@@ -144,8 +149,8 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (mediaData.isNotEmpty && text.isNotEmpty) const SizedBox(height: 8),
|
||||
if (text.isNotEmpty) Text(text, style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16)),
|
||||
if (mediaData.isNotEmpty && _getDisplayText().isNotEmpty) const SizedBox(height: 8),
|
||||
if (_getDisplayText().isNotEmpty) _buildLinkableText(_getDisplayText()),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -163,4 +168,88 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getDisplayText() {
|
||||
if (messageType == 'plan_order') {
|
||||
if (isConsultant == true) {
|
||||
// Текст для консультанта
|
||||
return '1. Уточните у подопечного его цели и пожелания\n'
|
||||
'2. Согласуйте и выберите дату старта плана\n'
|
||||
'3. Ожидайте уведомления об успешной оплате\n'
|
||||
'4. После оплаты предоставьте необходимые данные, согласно вашему плану питания';
|
||||
} else {
|
||||
// Текст для пользователя
|
||||
return 'Укажите вашу цель в работе с консультантом\n'
|
||||
'Согласуйте удобную дату старта плана, после чего вам будет направлена ссылка на оплату.';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
Widget _buildLinkableText(String text) {
|
||||
final urlRegex = RegExp(
|
||||
r'(?:(?:https?|ftp):\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
final textStyle = TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16);
|
||||
final linkStyle = textStyle.copyWith(
|
||||
color: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
);
|
||||
|
||||
final matches = urlRegex.allMatches(text);
|
||||
if (matches.isEmpty) {
|
||||
return Text(text, style: textStyle);
|
||||
}
|
||||
|
||||
final spans = <TextSpan>[];
|
||||
int lastEnd = 0;
|
||||
|
||||
for (final match in matches) {
|
||||
// Добавляем текст до ссылки
|
||||
if (match.start > lastEnd) {
|
||||
spans.add(TextSpan(
|
||||
text: text.substring(lastEnd, match.start),
|
||||
style: textStyle,
|
||||
));
|
||||
}
|
||||
|
||||
// Добавляем кликабельную ссылку
|
||||
final url = match.group(0)!;
|
||||
final fullUrl = url.startsWith('http://') || url.startsWith('https://') || url.startsWith('ftp://')
|
||||
? url
|
||||
: 'https://$url';
|
||||
|
||||
spans.add(TextSpan(
|
||||
text: url,
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
try {
|
||||
final uri = Uri.parse(fullUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} catch (e) {
|
||||
// Игнорируем ошибки открытия ссылок
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
lastEnd = match.end;
|
||||
}
|
||||
|
||||
// Добавляем оставшийся текст после последней ссылки
|
||||
if (lastEnd < text.length) {
|
||||
spans.add(TextSpan(
|
||||
text: text.substring(lastEnd),
|
||||
style: textStyle,
|
||||
));
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class PlanOrderMessageWidget extends StatelessWidget {
|
||||
const PlanOrderMessageWidget({
|
||||
super.key,
|
||||
required this.planName,
|
||||
required this.planSlug,
|
||||
required this.price,
|
||||
required this.sendTime,
|
||||
required this.isCurrentUser,
|
||||
});
|
||||
|
||||
final String planName;
|
||||
final String planSlug;
|
||||
final dynamic price;
|
||||
final DateTime sendTime;
|
||||
final bool isCurrentUser;
|
||||
|
||||
String _formatPrice(dynamic price) {
|
||||
if (price == null) return '0₽';
|
||||
if (price is num) {
|
||||
return '${price.toStringAsFixed(0)}₽';
|
||||
}
|
||||
if (price is String) {
|
||||
final numPrice = num.tryParse(price);
|
||||
if (numPrice != null) {
|
||||
return '${numPrice.toStringAsFixed(0)}₽';
|
||||
}
|
||||
return price.endsWith('₽') ? price : '$price₽';
|
||||
}
|
||||
return '0₽';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
final formattedPrice = _formatPrice(price);
|
||||
final textStyle = TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16);
|
||||
final linkStyle = textStyle.copyWith(
|
||||
color: appTheme.blueA400,
|
||||
decoration: TextDecoration.underline,
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 10, isCurrentUser ? 16 : 64, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
|
||||
borderRadius: BorderRadius.horizontal(
|
||||
left: isCurrentUser ? const Radius.circular(32) : const Radius.circular(16),
|
||||
right: isCurrentUser ? const Radius.circular(16) : const Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
left: isCurrentUser ? 16 : 8,
|
||||
right: isCurrentUser ? 8 : 16,
|
||||
),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: textStyle,
|
||||
children: [
|
||||
const TextSpan(text: 'Я хочу заказать '),
|
||||
TextSpan(
|
||||
text: planName,
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'aboutPlanScreen',
|
||||
extra: {
|
||||
'slug': planSlug,
|
||||
'index': null,
|
||||
'navigateToComments': false,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
TextSpan(text: ' по цене $formattedPrice'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('HH:mm', 'ru'),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user