542 lines
17 KiB
Dart
542 lines
17 KiB
Dart
import 'package:extended_text_field/extended_text_field.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:yumi/app/config/business_logic_strategy.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_localizations.dart';
|
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.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_emoji_asset_image.dart';
|
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
|
|
|
///聊天输入框
|
|
class RoomMsgInput extends StatefulWidget {
|
|
final String? atTextContent;
|
|
final bool initialShowEmoji;
|
|
|
|
const RoomMsgInput({
|
|
super.key,
|
|
this.atTextContent,
|
|
this.initialShowEmoji = false,
|
|
});
|
|
|
|
@override
|
|
State<RoomMsgInput> createState() => _RoomMsgInputState();
|
|
}
|
|
|
|
class _RoomMsgInputState extends State<RoomMsgInput> {
|
|
static const List<_RoomEmojiCategory> _roomEmojiCategories = [
|
|
_RoomEmojiCategory(
|
|
id: "fluent",
|
|
iconResource: "sc_images/room/emoji/fluent_emoji_01.webp",
|
|
items: [
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_01.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_02.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_03.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_04.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_05.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_06.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_07.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_08.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_09.webp"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/fluent_emoji_10.webp"),
|
|
],
|
|
),
|
|
_RoomEmojiCategory(
|
|
id: "monkey",
|
|
iconResource: "sc_images/room/emoji/monkey_1.gif",
|
|
items: [
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_1.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_2.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_3.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_4.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_5.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_6.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_7.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_8.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_9.gif"),
|
|
_RoomEmojiItem.local("sc_images/room/emoji/monkey_10.gif"),
|
|
],
|
|
),
|
|
];
|
|
|
|
bool showSend = false;
|
|
bool showEmoji = false;
|
|
bool _emojiAssetsPrecached = false;
|
|
int _selectedEmojiCategoryIndex = 0;
|
|
|
|
final FocusNode msgNode = FocusNode();
|
|
final TextEditingController controller = TextEditingController();
|
|
|
|
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
|
Color get _panelBackgroundColor => Colors.white24;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
showEmoji = widget.initialShowEmoji;
|
|
if (widget.atTextContent != null) {
|
|
controller.value = TextEditingValue(text: widget.atTextContent ?? "");
|
|
showSend = controller.text.trim().isNotEmpty;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.dispose();
|
|
msgNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (showEmoji) {
|
|
_precacheRoomEmojis();
|
|
_loadRemoteRoomEmojis();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
|
return Scaffold(
|
|
backgroundColor: Colors.transparent,
|
|
resizeToAvoidBottomInset: false,
|
|
body: Column(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
),
|
|
AnimatedPadding(
|
|
duration: const Duration(milliseconds: 220),
|
|
curve: Curves.easeOut,
|
|
padding: EdgeInsets.only(bottom: keyboardHeight),
|
|
child: _buildBottomPanel(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()
|
|
: "sc_images/room/sc_icon_room_bottom_emoji.png",
|
|
color: showEmoji ? Colors.white : null,
|
|
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(
|
|
decoration: BoxDecoration(
|
|
color: _panelBackgroundColor,
|
|
borderRadius: BorderRadius.circular(height(5)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: ExtendedTextField(
|
|
controller: controller,
|
|
textDirection:
|
|
widget.atTextContent != null ? TextDirection.ltr : null,
|
|
specialTextSpanBuilder: AtTextSpanBuilder(),
|
|
focusNode: msgNode,
|
|
autofocus: !widget.initialShowEmoji,
|
|
textInputAction: TextInputAction.send,
|
|
onTap: () {
|
|
if (showEmoji) {
|
|
setState(() {
|
|
showEmoji = false;
|
|
});
|
|
}
|
|
},
|
|
onSubmitted: (_) {
|
|
_sendMessage();
|
|
},
|
|
onChanged: (s) {
|
|
setState(() {
|
|
showSend = controller.text.trim().isNotEmpty;
|
|
});
|
|
},
|
|
decoration: InputDecoration(
|
|
hintText: SCAppLocalizations.of(context)!.pleaseChatFfriendly,
|
|
fillColor: Colors.transparent,
|
|
hintStyle: TextStyle(color: Colors.white60, fontSize: sp(14)),
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 15.w),
|
|
counterText: '',
|
|
isDense: true,
|
|
filled: false,
|
|
focusColor: Colors.transparent,
|
|
hoverColor: Colors.transparent,
|
|
border: InputBorder.none,
|
|
enabledBorder: InputBorder.none,
|
|
focusedBorder: InputBorder.none,
|
|
disabledBorder: InputBorder.none,
|
|
errorBorder: InputBorder.none,
|
|
focusedErrorBorder: InputBorder.none,
|
|
),
|
|
style: TextStyle(
|
|
fontSize: ScreenUtil().setSp(14),
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: _sendMessage,
|
|
child: Opacity(
|
|
opacity: showSend ? 1 : 0.5,
|
|
child: Container(
|
|
padding: EdgeInsets.all(4.w),
|
|
width: 30.w,
|
|
height: 30.w,
|
|
child: Image.asset(
|
|
"sc_images/room/sc_icon_room_message_send.png",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 3.w),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmojiPanel() {
|
|
final categories = _resolveRoomEmojiCategories();
|
|
final selectedIndex = _selectedEmojiCategoryIndex.clamp(
|
|
0,
|
|
categories.length - 1,
|
|
);
|
|
final selectedCategory = categories[selectedIndex];
|
|
return Container(
|
|
height: 220.w,
|
|
decoration: BoxDecoration(
|
|
color: _panelBackgroundColor,
|
|
borderRadius: BorderRadius.circular(height(5)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: GridView.builder(
|
|
key: ValueKey(selectedCategory.id),
|
|
padding: EdgeInsets.fromLTRB(18.w, 14.w, 18.w, 10.w),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 4,
|
|
crossAxisSpacing: 18.w,
|
|
mainAxisSpacing: 12.w,
|
|
),
|
|
itemCount: selectedCategory.items.length,
|
|
itemBuilder: (context, index) {
|
|
final emojiItem = selectedCategory.items[index];
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
_sendRoomEmoji(emojiItem.sendResource);
|
|
},
|
|
child: Center(
|
|
child: RoomEmojiAssetImage(
|
|
key: ValueKey(emojiItem.displayResource),
|
|
asset: emojiItem.displayResource,
|
|
width: 42.w,
|
|
height: 42.w,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
_buildEmojiCategoryBar(categories, selectedIndex),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmojiCategoryBar(
|
|
List<_RoomEmojiCategory> categories,
|
|
int selectedIndex,
|
|
) {
|
|
return Container(
|
|
height: 50.w,
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 7.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withValues(alpha: 0.08),
|
|
border: Border(
|
|
top: BorderSide(color: Colors.white.withValues(alpha: 0.08)),
|
|
),
|
|
),
|
|
child: ListView.separated(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: categories.length,
|
|
separatorBuilder: (_, __) => SizedBox(width: 10.w),
|
|
itemBuilder: (context, index) {
|
|
final category = categories[index];
|
|
final selected = index == selectedIndex;
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
if (selected) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_selectedEmojiCategoryIndex = index;
|
|
});
|
|
},
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 160),
|
|
curve: Curves.easeOut,
|
|
width: 64.w,
|
|
height: 36.w,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
selected
|
|
? Colors.white.withValues(alpha: 0.22)
|
|
: Colors.white.withValues(alpha: 0.08),
|
|
borderRadius: BorderRadius.circular(18.w),
|
|
border: Border.all(
|
|
color:
|
|
selected
|
|
? Colors.white.withValues(alpha: 0.30)
|
|
: Colors.transparent,
|
|
width: 1.w,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: RoomEmojiAssetImage(
|
|
key: ValueKey("emoji_category_${category.id}"),
|
|
asset: category.iconResource,
|
|
width: 26.w,
|
|
height: 26.w,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _toggleEmojiPanel() {
|
|
if (showEmoji) {
|
|
setState(() {
|
|
showEmoji = false;
|
|
});
|
|
FocusScope.of(context).requestFocus(msgNode);
|
|
return;
|
|
}
|
|
|
|
SCKeybordUtil.hide(context);
|
|
_precacheRoomEmojis();
|
|
_loadRemoteRoomEmojis();
|
|
setState(() {
|
|
showEmoji = true;
|
|
});
|
|
}
|
|
|
|
void _precacheRoomEmojis() {
|
|
if (_emojiAssetsPrecached) {
|
|
return;
|
|
}
|
|
_emojiAssetsPrecached = true;
|
|
for (final category in _roomEmojiCategories) {
|
|
precacheImage(AssetImage(category.iconResource), context);
|
|
for (final item in category.items) {
|
|
precacheImage(AssetImage(item.displayResource), context);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _loadRemoteRoomEmojis() {
|
|
Provider.of<SCAppGeneralManager>(context, listen: false).emojiAll();
|
|
}
|
|
|
|
List<_RoomEmojiCategory> _resolveRoomEmojiCategories() {
|
|
final remoteGroups =
|
|
Provider.of<SCAppGeneralManager>(context).roomEmojiGroups;
|
|
return [
|
|
..._roomEmojiCategories,
|
|
...remoteGroups
|
|
.map(_RoomEmojiCategory.fromRemote)
|
|
.where((category) => category.items.isNotEmpty),
|
|
];
|
|
}
|
|
|
|
void _sendRoomEmoji(String emojiAsset) {
|
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
|
final currenRoom = rtcProvider.currenRoom;
|
|
final currentUser = AccountStorage().getCurrentUser()?.userProfile;
|
|
if (currenRoom == null || currentUser == null) {
|
|
return;
|
|
}
|
|
|
|
final seatIndex = rtcProvider.userOnMaiInIndex(currentUser.id ?? "");
|
|
final msg = Msg(
|
|
groupId: currenRoom.roomProfile?.roomProfile?.roomAccount ?? "",
|
|
role: rtcProvider.currenRoom?.entrants?.roles ?? "",
|
|
msg: emojiAsset,
|
|
type: SCRoomMsgType.emoticons,
|
|
user: currentUser,
|
|
number: seatIndex,
|
|
);
|
|
|
|
if (seatIndex > -1) {
|
|
rtcProvider.starPlayEmoji(msg);
|
|
}
|
|
Provider.of<RtmProvider>(
|
|
context,
|
|
listen: false,
|
|
).dispatchMessage(msg, addLocal: seatIndex < 0);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
class _RoomEmojiCategory {
|
|
const _RoomEmojiCategory({
|
|
required this.id,
|
|
required this.iconResource,
|
|
required this.items,
|
|
});
|
|
|
|
factory _RoomEmojiCategory.fromRemote(SCRoomEmojiRes group) {
|
|
final items =
|
|
(group.emojis ?? const <Emojis>[])
|
|
.map(_RoomEmojiItem.fromRemote)
|
|
.where((item) => item.sendResource.isNotEmpty)
|
|
.toList();
|
|
final firstPreview =
|
|
items.isEmpty ? "" : items.first.displayResource.trim();
|
|
final cover = group.cover?.trim() ?? "";
|
|
final remoteId =
|
|
(group.id ?? group.groupCode ?? group.groupName ?? firstPreview).trim();
|
|
return _RoomEmojiCategory(
|
|
id: "remote_${remoteId.isEmpty ? firstPreview : remoteId}",
|
|
iconResource: cover.isNotEmpty ? cover : firstPreview,
|
|
items: items,
|
|
);
|
|
}
|
|
|
|
final String id;
|
|
final String iconResource;
|
|
final List<_RoomEmojiItem> items;
|
|
}
|
|
|
|
class _RoomEmojiItem {
|
|
const _RoomEmojiItem({
|
|
required this.displayResource,
|
|
required this.sendResource,
|
|
});
|
|
|
|
const _RoomEmojiItem.local(String resource)
|
|
: displayResource = resource,
|
|
sendResource = resource;
|
|
|
|
factory _RoomEmojiItem.fromRemote(Emojis emoji) {
|
|
final cover = emoji.coverUrl?.trim() ?? "";
|
|
final source = emoji.sourceUrl?.trim() ?? "";
|
|
return _RoomEmojiItem(
|
|
displayResource: cover.isNotEmpty ? cover : source,
|
|
sendResource: source.isNotEmpty ? source : cover,
|
|
);
|
|
}
|
|
|
|
final String displayResource;
|
|
final String sendResource;
|
|
}
|
|
|
|
class PopRoute extends PopupRoute {
|
|
final Duration _duration = Duration(milliseconds: 350);
|
|
Widget child;
|
|
|
|
PopRoute({required this.child});
|
|
|
|
@override
|
|
Color? get barrierColor => null;
|
|
|
|
@override
|
|
bool get barrierDismissible => true;
|
|
|
|
@override
|
|
String? get barrierLabel => null;
|
|
|
|
@override
|
|
Widget buildPage(
|
|
BuildContext context,
|
|
Animation<double> animation,
|
|
Animation<double> secondaryAnimation,
|
|
) {
|
|
return child;
|
|
}
|
|
|
|
@override
|
|
Duration get transitionDuration => _duration;
|
|
}
|