emoji添加
This commit is contained in:
parent
88d9428970
commit
e927bf4520
@ -1,72 +1,129 @@
|
|||||||
import 'dart:ui' as ui;
|
import 'package:extended_text/extended_text.dart';
|
||||||
|
|
||||||
import 'package:extended_text_field/extended_text_field.dart';
|
import 'package:extended_text_field/extended_text_field.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
|
||||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_emoji_datas.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||||
import 'package:yumi/app/constants/sc_screen.dart';
|
import 'package:yumi/app/constants/sc_screen.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_keybord_util.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
|
|
||||||
///聊天输入框
|
///聊天输入框
|
||||||
class RoomMsgInput extends StatefulWidget {
|
class RoomMsgInput extends StatefulWidget {
|
||||||
String? atTextContent;
|
final String? atTextContent;
|
||||||
|
|
||||||
RoomMsgInput({this.atTextContent});
|
const RoomMsgInput({super.key, this.atTextContent});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RoomMsgInputState createState() => _RoomMsgInputState();
|
State<RoomMsgInput> createState() => _RoomMsgInputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RoomMsgInputState extends State<RoomMsgInput> {
|
class _RoomMsgInputState extends State<RoomMsgInput> {
|
||||||
bool showSend = false;
|
bool showSend = false;
|
||||||
|
bool showEmoji = false;
|
||||||
|
|
||||||
FocusNode msgNode = FocusNode();
|
final FocusNode msgNode = FocusNode();
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
TextEditingController controller = TextEditingController();
|
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||||
|
Color get _panelBackgroundColor => Colors.white24;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.atTextContent != null) {
|
if (widget.atTextContent != null) {
|
||||||
controller.value = TextEditingValue(text: widget.atTextContent ?? "");
|
controller.value = TextEditingValue(text: widget.atTextContent ?? "");
|
||||||
|
showSend = controller.text.trim().isNotEmpty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
msgNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// FocusScope.of(context).requestFocus(msgNode);
|
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
float(context),
|
AnimatedPadding(
|
||||||
|
duration: const Duration(milliseconds: 220),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
padding: EdgeInsets.only(bottom: keyboardHeight),
|
||||||
|
child: _buildBottomPanel(context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget float(BuildContext context) {
|
Widget _buildBottomPanel(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.black,
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(25.w, 12.w, 25.w, 12.w),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: _toggleEmojiPanel,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 24.w,
|
||||||
|
height: 24.w,
|
||||||
|
child: Image.asset(
|
||||||
|
showEmoji
|
||||||
|
? _strategy.getSCMessageChatPageChatKeyboardIcon()
|
||||||
|
: _strategy.getSCMessageChatPageEmojiIcon(),
|
||||||
|
color: Colors.white,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 15.w),
|
||||||
|
Expanded(child: _buildInputBar(context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (showEmoji) SizedBox(height: 12.w),
|
||||||
|
if (showEmoji) _buildEmojiPanel(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInputBar(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(color: Colors.black),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 25.w, vertical: 12.w),
|
|
||||||
height: 56.0,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white24,
|
color: _panelBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(height(5)),
|
borderRadius: BorderRadius.circular(height(5)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -79,47 +136,27 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
specialTextSpanBuilder: AtTextSpanBuilder(),
|
specialTextSpanBuilder: AtTextSpanBuilder(),
|
||||||
focusNode: msgNode,
|
focusNode: msgNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onSubmitted: (s) {
|
textInputAction: TextInputAction.send,
|
||||||
Navigator.pop(context);
|
onTap: () {
|
||||||
if (controller.text.isNotEmpty) {
|
if (showEmoji) {
|
||||||
var currenRoom =
|
setState(() {
|
||||||
Provider.of<RtcProvider>(
|
showEmoji = false;
|
||||||
context,
|
});
|
||||||
listen: false,
|
|
||||||
).currenRoom;
|
|
||||||
if (currenRoom != null) {
|
|
||||||
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
|
||||||
Msg(
|
|
||||||
groupId:
|
|
||||||
currenRoom
|
|
||||||
.roomProfile
|
|
||||||
?.roomProfile
|
|
||||||
?.roomAccount ??
|
|
||||||
"",
|
|
||||||
role:
|
|
||||||
Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).currenRoom?.entrants?.roles ??
|
|
||||||
"",
|
|
||||||
msg: controller.text,
|
|
||||||
type: SCRoomMsgType.text,
|
|
||||||
user: AccountStorage().getCurrentUser()?.userProfile,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onSubmitted: (_) {
|
||||||
|
_sendMessage();
|
||||||
|
},
|
||||||
onChanged: (s) {
|
onChanged: (s) {
|
||||||
setState(() {
|
setState(() {
|
||||||
showSend = controller.text.isNotEmpty;
|
showSend = controller.text.trim().isNotEmpty;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: SCAppLocalizations.of(context)!.pleaseChatFfriendly,
|
hintText: SCAppLocalizations.of(context)!.pleaseChatFfriendly,
|
||||||
fillColor: Colors.transparent,
|
fillColor: Colors.transparent,
|
||||||
hintStyle: TextStyle(color: Colors.white60, fontSize: sp(14)),
|
hintStyle: TextStyle(color: Colors.white60, fontSize: sp(14)),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 15),
|
contentPadding: EdgeInsets.symmetric(horizontal: 15.w),
|
||||||
counterText: '',
|
counterText: '',
|
||||||
isDense: true,
|
isDense: true,
|
||||||
filled: false,
|
filled: false,
|
||||||
@ -139,47 +176,114 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
behavior: HitTestBehavior.opaque,
|
||||||
Navigator.pop(context);
|
onTap: _sendMessage,
|
||||||
if (controller.text.isNotEmpty) {
|
child: Opacity(
|
||||||
var currenRoom =
|
opacity: showSend ? 1 : 0.5,
|
||||||
Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).currenRoom;
|
|
||||||
if (currenRoom != null) {
|
|
||||||
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
|
||||||
Msg(
|
|
||||||
groupId:
|
|
||||||
currenRoom.roomProfile?.roomProfile?.roomAccount ??
|
|
||||||
"",
|
|
||||||
role:
|
|
||||||
Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).currenRoom?.entrants?.roles ??
|
|
||||||
"",
|
|
||||||
msg: controller.text,
|
|
||||||
type: SCRoomMsgType.text,
|
|
||||||
user: AccountStorage().getCurrentUser()?.userProfile,
|
|
||||||
),
|
|
||||||
addLocal: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(4.w),
|
padding: EdgeInsets.all(4.w),
|
||||||
width: 30.w,
|
width: 30.w,
|
||||||
height: 30.w,
|
height: 30.w,
|
||||||
child: Image.asset("sc_images/room/sc_icon_room_message_send.png"),
|
child: Image.asset(
|
||||||
|
"sc_images/room/sc_icon_room_message_send.png",
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 3.w),
|
SizedBox(width: 3.w),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmojiPanel() {
|
||||||
|
return Container(
|
||||||
|
height: 150.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _panelBackgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(height(5)),
|
||||||
|
),
|
||||||
|
child: GridView.builder(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 8,
|
||||||
|
crossAxisSpacing: 8.w,
|
||||||
|
mainAxisSpacing: 8.w,
|
||||||
|
),
|
||||||
|
itemCount: SCEmojiDatas.smileys.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final emoji = SCEmojiDatas.smileys[index];
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
_insertEmoji(emoji);
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: ExtendedText(emoji, style: TextStyle(fontSize: sp(15))),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleEmojiPanel() {
|
||||||
|
if (showEmoji) {
|
||||||
|
setState(() {
|
||||||
|
showEmoji = false;
|
||||||
|
});
|
||||||
|
FocusScope.of(context).requestFocus(msgNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCKeybordUtil.hide(context);
|
||||||
|
setState(() {
|
||||||
|
showEmoji = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _insertEmoji(String emoji) {
|
||||||
|
final value = controller.value;
|
||||||
|
final selection = value.selection;
|
||||||
|
final fallbackOffset = value.text.length;
|
||||||
|
final start = selection.isValid ? selection.start : fallbackOffset;
|
||||||
|
final end = selection.isValid ? selection.end : fallbackOffset;
|
||||||
|
final safeStart = start < 0 ? fallbackOffset : start;
|
||||||
|
final safeEnd = end < 0 ? fallbackOffset : end;
|
||||||
|
final nextText = value.text.replaceRange(safeStart, safeEnd, emoji);
|
||||||
|
|
||||||
|
controller.value = value.copyWith(
|
||||||
|
text: nextText,
|
||||||
|
selection: TextSelection.collapsed(offset: safeStart + emoji.length),
|
||||||
|
composing: TextRange.empty,
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
showSend = controller.text.trim().isNotEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendMessage() {
|
||||||
|
if (controller.text.trim().isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
|
final currenRoom = rtcProvider.currenRoom;
|
||||||
|
if (currenRoom == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
||||||
|
Msg(
|
||||||
|
groupId: currenRoom.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||||
|
role: rtcProvider.currenRoom?.entrants?.roles ?? "",
|
||||||
|
msg: controller.text,
|
||||||
|
type: SCRoomMsgType.text,
|
||||||
|
user: AccountStorage().getCurrentUser()?.userProfile,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
需求进度.md
7
需求进度.md
@ -30,6 +30,7 @@
|
|||||||
- 已定位 `FOREGROUND_SERVICE_MEDIA_PROJECTION` 的来源为 Agora `full-screen-sharing` 依赖,并开始从主 Manifest 覆盖移除。
|
- 已定位 `FOREGROUND_SERVICE_MEDIA_PROJECTION` 的来源为 Agora `full-screen-sharing` 依赖,并开始从主 Manifest 覆盖移除。
|
||||||
- 已移除 `FOREGROUND_SERVICE_MEDIA_PROJECTION` 相关声明并重新打出正式 `AAB`。
|
- 已移除 `FOREGROUND_SERVICE_MEDIA_PROJECTION` 相关声明并重新打出正式 `AAB`。
|
||||||
- 已按当前需求打出正式 `arm64-v8a` 单架构 `APK`。
|
- 已按当前需求打出正式 `arm64-v8a` 单架构 `APK`。
|
||||||
|
- 已将新的 `google-services.json` 替换进工程,准备重新打包验证。
|
||||||
|
|
||||||
## 进行中模块
|
## 进行中模块
|
||||||
- 评估 iOS 正式签名参数接入后导出 `.ipa` 的流程。
|
- 评估 iOS 正式签名参数接入后导出 `.ipa` 的流程。
|
||||||
@ -54,6 +55,7 @@
|
|||||||
- `pubspec.lock`
|
- `pubspec.lock`
|
||||||
- `lib/ui_kit/components/dialog/dialog.dart`
|
- `lib/ui_kit/components/dialog/dialog.dart`
|
||||||
- `android/app/src/main/AndroidManifest.xml`
|
- `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- `android/app/google-services.json`
|
||||||
- `ios/Runner/Info.plist`
|
- `ios/Runner/Info.plist`
|
||||||
- `scripts/tinypng_batch.rb`
|
- `scripts/tinypng_batch.rb`
|
||||||
- `scripts/build_release.py`
|
- `scripts/build_release.py`
|
||||||
@ -160,6 +162,11 @@
|
|||||||
- 本轮按需执行 `flutter build apk --release --split-per-abi --target-platform android-arm64 --split-debug-info=build/symbols/android` 已成功,产物为:
|
- 本轮按需执行 `flutter build apk --release --split-per-abi --target-platform android-arm64 --split-debug-info=build/symbols/android` 已成功,产物为:
|
||||||
- `build/app/outputs/flutter-apk/app-arm64-v8a-release.apk`
|
- `build/app/outputs/flutter-apk/app-arm64-v8a-release.apk`
|
||||||
- 当前 `arm64-v8a` APK 体积约 `128.7MB`
|
- 当前 `arm64-v8a` APK 体积约 `128.7MB`
|
||||||
|
- 已核对新旧 `google-services.json`:
|
||||||
|
- 原文件与新文件都匹配包名 `com.org.yumi`
|
||||||
|
- 下载文件已成功覆盖到 `android/app/google-services.json`
|
||||||
|
- 当前工程内文件 `SHA256` 为:
|
||||||
|
- `a1706496a01f74d27e6c60598144cecc978934b02a87567e1ced887b5b0185d5`
|
||||||
|
|
||||||
## 已知问题
|
## 已知问题
|
||||||
- `ios/Podfile.lock` 还保留旧插件记录,因为本轮未执行 `pod install`;但 Flutter 当前依赖链路已经不再包含已移除插件。
|
- `ios/Podfile.lock` 还保留旧插件记录,因为本轮未执行 `pod install`;但 Flutter 当前依赖链路已经不再包含已移除插件。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user