Browse Source

fix(call): 添加忙线提示并优化聊天输入框表情显示

- 添加对方忙线中提示功能
- 移除过期的extended_text_field依赖
- 实现新的表情显示机制替换原有特殊文本构建器
- 重构聊天输入框表情处理逻辑
- 优化输入框光标位置管理
- 添加表情输入内容构建方法
master
Jolie 2 months ago
parent
commit
eca0a3fd5a
3 changed files with 103 additions and 159 deletions
  1. 4
      lib/controller/message/call_controller.dart
  2. 257
      lib/widget/message/chat_input_bar.dart
  3. 1
      pubspec.yaml

4
lib/controller/message/call_controller.dart

@ -184,6 +184,10 @@ class CallController extends GetxController {
}); });
return null; return null;
} }
if (!response.data.data!.success && response.data.data!.code == 'E0003') {
SmartDialog.showToast('对方忙线中');
return null;
}
rtcChannel.value = response.data.data; rtcChannel.value = response.data.data;
print('✅ 创建一对一RTC频道成功: ${response.data.data?.channelId}'); print('✅ 创建一对一RTC频道成功: ${response.data.data?.channelId}');
isCreatingChannel.value = false; isCreatingChannel.value = false;

257
lib/widget/message/chat_input_bar.dart

@ -1,8 +1,6 @@
import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:extended_text_field/extended_text_field.dart';
import '../../generated/assets.dart'; import '../../generated/assets.dart';
import '../../config/emoji_config.dart'; import '../../config/emoji_config.dart';
@ -10,135 +8,6 @@ import '../emoji_panel.dart';
import 'more_options_view.dart'; import 'more_options_view.dart';
import 'voice_input_view.dart'; import 'voice_input_view.dart';
/// - ExtendedTextField
class EmojiSpecialTextSpanBuilder extends SpecialTextSpanBuilder {
@override
SpecialText? createSpecialText(
String flag, {
TextStyle? textStyle,
SpecialTextGestureTapCallback? onTap,
required int index,
}) {
// [emoji:xxx]
if (flag.startsWith('[emoji:')) {
return EmojiSpecialText(
textStyle: textStyle,
onTap: onTap,
);
}
return null;
}
}
///
class EmojiSpecialText extends SpecialText {
EmojiSpecialText({
TextStyle? textStyle,
SpecialTextGestureTapCallback? onTap,
}) : super(
'[emoji:',
']',
textStyle,
onTap: onTap,
);
@override
InlineSpan finishText() {
// ID
final emojiId = toString().replaceAll('[emoji:', '').replaceAll(']', '');
final emoji = EmojiConfig.getEmojiById(emojiId);
if (emoji != null) {
// WidgetSpan
return WidgetSpan(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 2.w),
child: Image.asset(
emoji.path,
width: 24.w,
height: 24.w,
fit: BoxFit.contain,
),
),
);
}
//
return TextSpan(
text: toString(),
style: textStyle,
);
}
}
/// -
class EmojiTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
//
if (newValue.text.length < oldValue.text.length) {
final oldSelection = oldValue.selection;
final cursorOffset = oldSelection.baseOffset;
final deletedLength = oldValue.text.length - newValue.text.length;
//
final emojiRegex = RegExp(r'\[emoji:\d+\]');
final allMatches = emojiRegex.allMatches(oldValue.text);
//
for (final match in allMatches) {
final emojiStart = match.start;
final emojiEnd = match.end;
//
if (cursorOffset > emojiStart && cursorOffset <= emojiEnd) {
//
final beforeEmoji = oldValue.text.substring(0, emojiStart);
final afterEmoji = oldValue.text.substring(emojiEnd);
final newText = beforeEmoji + afterEmoji;
//
//
// = -
final emojiLength = emojiEnd - emojiStart;
int newCursorOffset = (cursorOffset - emojiLength).clamp(0, newText.length);
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newCursorOffset),
);
}
//
if (cursorOffset == emojiStart) {
//
if (cursorOffset + deletedLength <= emojiEnd) {
//
final beforeEmoji = oldValue.text.substring(0, emojiStart);
final afterEmoji = oldValue.text.substring(emojiEnd);
final newText = beforeEmoji + afterEmoji;
//
// = -
final emojiLength = emojiEnd - emojiStart;
int newCursorOffset = (cursorOffset - emojiLength).clamp(0, newText.length);
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newCursorOffset),
);
}
}
}
}
return newValue;
}
}
class ChatInputBar extends StatefulWidget { class ChatInputBar extends StatefulWidget {
final ValueChanged<String> onSendMessage; final ValueChanged<String> onSendMessage;
final ValueChanged<List<String>>? onImageSelected; final ValueChanged<List<String>>? onImageSelected;
@ -260,15 +129,71 @@ class _ChatInputBarState extends State<ChatInputBar> {
// //
final currentText = _textController.text; final currentText = _textController.text;
final emojiText = '[emoji:${emoji.id}]'; final emojiText = '[emoji:${emoji.id}]';
final newText = currentText + emojiText;
_textController.text = newText;
_textController.text = currentText + emojiText;
// //
_textController.selection = TextSelection.fromPosition( _textController.selection = TextSelection.fromPosition(
TextPosition(offset: newText.length),
TextPosition(offset: _textController.text.length),
); );
setState(() {}); // setState(() {}); //
} }
/// +
List<Widget> _buildInputContentWidgets() {
final List<Widget> widgets = [];
final text = _textController.text;
final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]');
int lastMatchEnd = 0;
final matches = emojiRegex.allMatches(text);
for (final match in matches) {
//
if (match.start > lastMatchEnd) {
final textPart = text.substring(lastMatchEnd, match.start);
widgets.add(
Text(
textPart,
style: TextStyle(fontSize: 14.sp, color: Colors.black),
),
);
}
//
final emojiId = match.group(1);
if (emojiId != null) {
final emoji = EmojiConfig.getEmojiById(emojiId);
if (emoji != null) {
widgets.add(
Padding(
padding: EdgeInsets.symmetric(horizontal: 2.w),
child: Image.asset(
emoji.path,
width: 24.w,
height: 24.w,
fit: BoxFit.contain,
),
),
);
}
}
lastMatchEnd = match.end;
}
//
if (lastMatchEnd < text.length) {
final textPart = text.substring(lastMatchEnd);
widgets.add(
Text(
textPart,
style: TextStyle(fontSize: 14.sp, color: Colors.black),
),
);
}
return widgets;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -290,32 +215,46 @@ class _ChatInputBarState extends State<ChatInputBar> {
borderRadius: BorderRadius.circular(5.h), borderRadius: BorderRadius.circular(5.h),
), ),
padding: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w),
alignment: Alignment.center,
child: ExtendedTextField(
controller: _textController,
focusNode: _focusNode,
specialTextSpanBuilder: EmojiSpecialTextSpanBuilder(),
inputFormatters: [
EmojiTextInputFormatter(),
],
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "请输入聊天内容~",
hintStyle: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
child: Stack(
children: [
//
TextField(
controller: _textController,
focusNode: _focusNode,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "请输入聊天内容~",
hintStyle: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
style: TextStyle(
fontSize: 14.sp,
color: _textController.text.contains('[emoji:')
? Colors.transparent
: Colors.black,
),
onChanged: (value) {
setState(() {}); //
},
), ),
isDense: true,
contentPadding: EdgeInsets.symmetric(vertical: 0),
),
style: TextStyle(
fontSize: 14.sp,
color: Colors.black,
),
onChanged: (value) {
setState(() {}); //
},
//
if (_textController.text.contains('[emoji:'))
Positioned.fill(
child: IgnorePointer(
child: Align(
alignment: Alignment.centerLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildInputContentWidgets(),
),
),
),
),
),
],
), ),
), ),
), ),

1
pubspec.yaml

@ -82,6 +82,7 @@ dependencies:
flutter_local_notifications: ^19.5.0 flutter_local_notifications: ^19.5.0
app_badge_plus: ^1.2.6 app_badge_plus: ^1.2.6
extended_text_field: ^16.0.2 extended_text_field: ^16.0.2
emoji_text_field: ^1.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save