Browse Source

feat(chat): 替换文本输入框为扩展文本字段以支持表情显示

- 集成 extended_text 和 extended_text_field 依赖包
- 移除自定义表情渲染逻辑和 _buildInputContentWidgets 方法
- 将 TextField 替换为 ExtendedTextField 组件
- 添加 MySpecialTextSpanBuilder 类处理特殊文本标记
- 实现 MyEmojiText 类解析 [emoji:id] 格式并渲染表情图片
- 配置 ImageSpan 显示表情图像并设置合适的尺寸
- 添加输入格式化器过滤零宽空格字符
master
Jolie 2 months ago
parent
commit
a78693c6e7
2 changed files with 93 additions and 76 deletions
  1. 168
      lib/widget/message/chat_input_bar.dart
  2. 1
      pubspec.yaml

168
lib/widget/message/chat_input_bar.dart

@ -1,5 +1,8 @@
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:extended_text/extended_text.dart';
import 'package:extended_text_field/extended_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../generated/assets.dart';
@ -137,63 +140,6 @@ class _ChatInputBarState extends State<ChatInputBar> {
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
Widget build(BuildContext context) {
return Column(
@ -218,7 +164,7 @@ class _ChatInputBarState extends State<ChatInputBar> {
child: Stack(
children: [
//
TextField(
ExtendedTextField(
controller: _textController,
focusNode: _focusNode,
decoration: InputDecoration(
@ -229,31 +175,19 @@ class _ChatInputBarState extends State<ChatInputBar> {
color: Colors.grey,
),
),
inputFormatters: [
// Unicode的规则
FilteringTextInputFormatter.deny(RegExp(r'[\u200B]')), //
],
specialTextSpanBuilder: MySpecialTextSpanBuilder(),
style: TextStyle(
fontSize: 14.sp,
color: _textController.text.contains('[emoji:')
? Colors.transparent
: Colors.black,
color: Colors.black, // specialTextSpanBuilder
),
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(),
),
),
),
),
),
],
),
),
@ -344,3 +278,85 @@ class _ChatInputBarState extends State<ChatInputBar> {
);
}
}
/// - TIMUIKitTextField
class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder {
@override
SpecialText? createSpecialText(
String flag, {
TextStyle? textStyle,
SpecialTextGestureTapCallback? onTap,
required int index,
}) {
if (flag.isEmpty) {
return null;
}
// index start flag index - (flag.length - 1)
// 使 '[' startFlag EmojiText.flag = '['
if (isStart(flag, MyEmojiText.flag)) {
return MyEmojiText(
textStyle,
start: index - (MyEmojiText.flag.length - 1),
);
}
return null;
}
}
/// - TIMUIKitTextField EmojiText
class MyEmojiText extends SpecialText {
static const String flag = '[';
final int start;
MyEmojiText(
TextStyle? textStyle, {
required this.start,
}) : super(MyEmojiText.flag, ']', textStyle);
@override
InlineSpan finishText() {
// toString() "[emoji:11]"
final String key = toString();
// [emoji:]
final RegExp emojiPattern = RegExp(r'^\[emoji:(\d+)\]$');
final match = emojiPattern.firstMatch(key);
if (match != null && match.groupCount > 0) {
final emojiId = match.group(1);
if (emojiId != null && emojiId.isNotEmpty) {
final emoji = EmojiConfig.getEmojiById(emojiId);
if (emoji != null && emoji.path.isNotEmpty) {
// 使 ImageSpan TIMUIKitTextField
double size = 16;
final TextStyle ts = textStyle!;
if (ts.fontSize != null) {
// TIMUIKitTextField: size = ts.fontSize! * 1.44
// 使 flutter_screenutil
// fontSize 14.sp
size = ts.fontSize! * 1.44;
// sp ImageSpan
// flutter_screenutil .sp 使
} else {
// fontSize使 14.sp * 1.44
size = 14.w * 1.44;
}
return ImageSpan(
AssetImage(emoji.path),
actualText: key,
imageWidth: size,
imageHeight: size,
start: start,
margin: const EdgeInsets.all(0), //
);
}
}
}
//
return TextSpan(text: key, style: textStyle);
}
}

1
pubspec.yaml

@ -81,6 +81,7 @@ dependencies:
ota_update: ^7.1.0
flutter_local_notifications: ^19.5.0
app_badge_plus: ^1.2.6
extended_text: ^15.0.2
extended_text_field: ^16.0.2
emoji_text_field: ^1.0.0

Loading…
Cancel
Save