会话页面

This commit is contained in:
2022-10-26 14:05:58 +08:00
parent 099d2b4423
commit 76bd3f94fd
33 changed files with 2956 additions and 31 deletions

View File

@@ -0,0 +1,37 @@
class HistoryMessageDartConstant {
static const getCount = 20;
// ignore: constant_identifier_names
static const V2_TIM_IMAGE_TYPES = {
'ORIGINAL': 0,
'BIG': 1,
'SMALL': 2,
};
static Map<V2_TIM_IMAGE_TYPES_ENUM, List<String>> imgPriorMap = {
V2_TIM_IMAGE_TYPES_ENUM.original: oriImgPrior,
V2_TIM_IMAGE_TYPES_ENUM.big: bigImgPrior,
V2_TIM_IMAGE_TYPES_ENUM.small: smallImgPrior,
};
// 缩略图优先,大图次之,最后是原图
static const smallImgPrior = ['ORIGINAL', 'BIG', 'SMALL'];
// 大图优先,原图次之,最后是缩略图
static const bigImgPrior = ['SMALL', 'ORIGINAL', 'BIG'];
// 原图优先,大图次之,最后是缩略图
static const oriImgPrior = ['SMALL', 'BIG', 'ORIGINAL'];
// 视频、音频已读状态
static const int read = 1;
}
enum V2_TIM_IMAGE_TYPES_ENUM {
original,
big,
small,
}
enum IMG_PREVIEW_TYPE {
local,
url,
}

View File

@@ -1,5 +1,4 @@
import 'package:chat/models/upload_model.dart';
import 'package:chat/models/user_info_model.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/services/tim/friend_service.dart';
@@ -9,9 +8,6 @@ import 'package:get/get.dart';
class UserController extends GetxController {
static UserController get to => Get.find<UserController>();
/// 用户信息,这个数据,在更新用户资料的时候,也应该更新
Rx<UserInfoModel> userInfo = UserInfoModel.empty().obs;
Future<bool> updateNickname(String nickname) async {
var result = await UserProvider.updateNickname(nickname);
if (result) {

View File

@@ -10,6 +10,7 @@ class ContactInfoModel extends ISuspensionBean {
V2TimFriendInfo? friendInfo;
IconData? icon;
Color? color;
String? route;
ContactInfoModel({
required this.name,
@@ -19,6 +20,7 @@ class ContactInfoModel extends ISuspensionBean {
this.friendInfo,
this.icon,
this.color,
this.route,
});
@override

View File

@@ -1,3 +1,4 @@
import 'package:chat/models/im/search_user_model.dart';
import 'package:chat/models/upload_model.dart';
import 'package:chat/utils/network/http.dart';
import 'package:chat/utils/ui_tools.dart';
@@ -32,4 +33,25 @@ class UserProvider {
return false;
}
}
/// 从服务器查找用户
static Future<List<SearchUserModel>?> searchUser(String keyword) async {
try {
var json = await Http.get(
'user/search',
params: {
'keyword': keyword,
},
);
return List<SearchUserModel>.from(
json.map(
(x) => SearchUserModel.fromJson(x),
),
);
} catch (err) {
UiTools.toast(err.toString());
}
return null;
}
}

View File

@@ -24,6 +24,7 @@ abstract class ContactRoutes {
static const String friendProfile = '/contact/friend/profile';
static const String friendProfileMore = '/contact/friend/profile/more';
static const String friendRemark = '/contact/friend/remark';
static const String friendRequest = '/contact/friend/request';
static const String friendRequestApply = '/contact/friend/request/apply';
static const String friendRecommend = '/contact/friend/recommend';
static const String friendRecommendFriend =
@@ -31,6 +32,7 @@ abstract class ContactRoutes {
static const String friendRecommendGroup = '/contact/friend/recommend/group';
static const String group = '/contact/group';
static const String groupSearch = '/contact/group/search';
static const String groupQrCode = '/contact/group/qrCode';
static const String groupCreate = '/contact/group/create';
static const String groupNotification = '/contact/group/notification';

View File

@@ -1,9 +1,8 @@
import 'package:azlistview/azlistview.dart';
import 'package:chat/models/im/contact_info_model.dart';
import 'package:chat/models/im/search_user_model.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/services/tim_service.dart';
import 'package:chat/utils/im_tools.dart';
import 'package:chat/utils/network/http.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -35,6 +34,7 @@ class TimFriendService extends GetxService {
tagIndex: '@',
icon: Icons.person_add_alt,
color: Colors.amber,
route: ContactRoutes.friendRequest,
),
ContactInfoModel(
name: '群聊',
@@ -42,6 +42,7 @@ class TimFriendService extends GetxService {
tagIndex: '@',
icon: Icons.group,
color: Colors.green,
route: ContactRoutes.group,
),
ContactInfoModel(
name: '订阅消息',
@@ -243,25 +244,4 @@ class TimFriendService extends GetxService {
UiTools.toast(result.desc);
return false;
}
/// 从服务器查找用户
Future<List<SearchUserModel>?> searchUser(String keyword) async {
try {
var json = await Http.get(
'user/search',
params: {
'keyword': keyword,
},
);
return List<SearchUserModel>.from(
json.map(
(x) => SearchUserModel.fromJson(x),
),
);
} catch (err) {
UiTools.toast(err.toString());
}
return null;
}
}

View File

@@ -0,0 +1,66 @@
import 'dart:async';
import 'package:flutter_plugin_record_plus/const/play_state.dart';
import 'package:flutter_plugin_record_plus/const/response.dart';
import 'package:flutter_plugin_record_plus/index.dart';
typedef PlayStateListener = void Function(PlayState playState);
typedef SoundInterruptListener = void Function();
typedef ResponseListener = void Function(RecordResponse recordResponse);
class SoundPlayer {
static final FlutterPluginRecord _recorder = FlutterPluginRecord();
static SoundInterruptListener? _soundInterruptListener;
static bool isInited = false;
static initSoundPlayer() {
if (!isInited) {
_recorder.init();
isInited = true;
}
}
static play({required String url}) {
_recorder.stopPlay();
if (_soundInterruptListener != null) {
_soundInterruptListener!();
}
_recorder.playByPath(url, 'url');
}
static stop() {
_recorder.stopPlay();
}
static dispose() {
_recorder.dispose();
}
static StreamSubscription<PlayState> playStateListener(
{required PlayStateListener listener}) =>
_recorder.responsePlayStateController.listen(listener);
static setSoundInterruptListener(SoundInterruptListener listener) {
_soundInterruptListener = listener;
}
static removeSoundInterruptListener() {
_soundInterruptListener = null;
}
static StreamSubscription<RecordResponse> responseListener(
ResponseListener listener) =>
_recorder.response.listen(listener);
static StreamSubscription<RecordResponse> responseFromAmplitudeListener(
ResponseListener listener) =>
_recorder.responseFromAmplitude.listen(listener);
static startRecord() {
_recorder.start();
}
static stopRecord() {
_recorder.stop();
}
}

View File

@@ -1,6 +1,7 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/controllers/private_controller.dart';
import 'package:chat/models/im/search_user_model.dart';
import 'package:chat/providers/user_provider.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/services/tim/apply_service.dart';
import 'package:chat/services/tim/friend_service.dart';
@@ -38,7 +39,7 @@ class _ImFriendRequestState extends State<ContactFriendRequestPage> {
searchList = null;
});
} else {
var result = await TimFriendService.to.searchUser(e);
var result = await UserProvider.searchUser(e);
setState(() {
searchList = result;

View File

@@ -1,15 +1,101 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/routes/conversation_routes.dart';
import 'package:chat/services/tim/conversation_service.dart';
import 'package:chat/services/tim/group_service.dart';
import 'package:chat/views/home/widgets/group_avatar.dart';
import 'package:chat/widgets/custom_easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_info.dart';
class ContactGroupPage extends StatefulWidget {
const ContactGroupPage({Key? key}) : super(key: key);
@override
_ContactGroupPageState createState() => _ContactGroupPageState();
State<ContactGroupPage> createState() => _ContactGroupPageState();
}
class _ContactGroupPageState extends State<ContactGroupPage> {
@override
Widget build(BuildContext context) {
return Container();
return Scaffold(
appBar: AppBar(
title: const Text('群聊'),
actions: [
IconButton(
icon: const Icon(Icons.search_outlined),
onPressed: () {
Get.toNamed(ContactRoutes.groupSearch);
},
),
IconButton(
icon: const Icon(Icons.add_outlined),
onPressed: () {
Get.toNamed(ContactRoutes.groupCreate);
},
),
],
),
body: GetX<TimGroupService>(builder: (_) {
return EasyRefresh(
onRefresh: () async {
_.fetchList();
},
header: CustomEasyRefresh.header,
child: Column(
children: [
ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
V2TimGroupInfo group = _.groups[index];
return ListTile(
onTap: () async {
var conversation = await TimConversationService.to
.getById('group_' + group.groupID);
Get.toNamed(
ConversationRoutes.index,
arguments: {
'conversation': conversation,
},
);
},
leading: GroupAvatar(group.groupID),
tileColor: AppColors.white,
title: Text(group.groupName!),
subtitle: Text('成员数: ${group.memberCount}'),
);
},
separatorBuilder: (context, index) {
return const Divider(
height: 0,
indent: 72,
);
},
itemCount: _.groups.length,
),
const Divider(
height: 0,
indent: 72,
),
Container(
color: AppColors.white,
height: 54,
child: Center(
child: Text(
'${_.groups.length} 个群聊',
style: const TextStyle(
color: AppColors.unactive,
),
),
),
),
],
),
);
}),
);
}
}

View File

@@ -48,7 +48,11 @@ class _ContactPageState extends State<ContactPage> {
return Column(
children: [
ListTile(
onTap: () async {},
onTap: () async {
if (info.route != null) {
Get.toNamed(info.route!);
}
},
leading: Container(
width: 40,
height: 40,

View File

@@ -0,0 +1,78 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/constants/message_constant.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
class PreviewImageWidget extends StatelessWidget {
final IMG_PREVIEW_TYPE type;
final String path;
final String original;
const PreviewImageWidget({
Key? key,
required this.type,
required this.path,
required this.original,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: AppColors.black,
child: SafeArea(
child: Stack(
children: [
Positioned.fill(
child: PhotoViewGallery.builder(
pageController: PageController(initialPage: 0),
itemCount: 1,
builder: (context, index) {
if (type == IMG_PREVIEW_TYPE.local) {
return PhotoViewGalleryPageOptions(
imageProvider: FileImage(File(path)),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 2,
);
} else {
if (path.split('?').first.isImageFileName) {
return PhotoViewGalleryPageOptions(
imageProvider: CachedNetworkImageProvider(path),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 2,
);
} else {
return PhotoViewGalleryPageOptions.customChild(
child: const Center(
child: Text(
'格式不支持',
style: TextStyle(
color: AppColors.white,
),
),
),
);
}
}
},
),
),
const SafeArea(
child: Padding(
padding: EdgeInsets.all(4.0),
child: BackButton(
color: AppColors.white,
),
),
)
],
),
),
);
}
}

View File

@@ -0,0 +1,119 @@
import 'dart:io';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:flutter/material.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_video_elem.dart';
import 'package:video_player/video_player.dart';
class PreviewVideoWidget extends StatefulWidget {
final V2TimVideoElem video;
const PreviewVideoWidget(this.video, {Key? key}) : super(key: key);
@override
State<PreviewVideoWidget> createState() => _PreviewVideoWidgetState();
}
class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
late VideoPlayerController _controller;
bool isPause = false;
@override
void initState() {
super.initState();
var lv = widget.video.localVideoUrl;
if (lv != null && lv.isNotEmpty && File(lv).existsSync()) {
_controller =
VideoPlayerController.file(File(widget.video.localVideoUrl!))
..initialize().then((value) {
setState(() {
_controller.play();
});
}).catchError((e) {
UiTools.toast(e.toString());
});
} else {
_controller = VideoPlayerController.network(widget.video.videoUrl!)
..initialize().then((value) {
setState(() {
_controller.play();
});
}).catchError((e) {
UiTools.toast(e.toString());
});
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBlack,
appBar: AppBar(
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.white,
),
body: GestureDetector(
onTap: () {
setState(() {
if (_controller.value.isInitialized) {
if (_controller.value.isPlaying) {
_controller.pause();
isPause = true;
} else {
_controller.play();
isPause = false;
}
}
});
},
child: Stack(
children: [
Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: _controller.value.hasError
? const Text(
'视频加载失败',
style: TextStyle(
color: AppColors.white,
),
)
: const CircularProgressIndicator(
color: AppColors.primary,
),
),
Align(
alignment: Alignment.bottomCenter,
child: VideoProgressIndicator(
_controller,
allowScrubbing: true,
),
),
Align(
alignment: Alignment.center,
child: Visibility(
visible: isPause,
child: const Icon(
Icons.play_circle_outline,
size: 96,
color: AppColors.white,
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:chat/services/tim/message_service.dart';
import 'package:chat/views/conversation/widgets/message_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class MessageArea extends StatefulWidget {
final V2TimConversation conversation;
const MessageArea(this.conversation, {Key? key}) : super(key: key);
@override
State<MessageArea> createState() => _MessageAreaState();
}
class _MessageAreaState extends State<MessageArea> {
late AutoScrollController _scrollController;
String? _lastMessageId;
@override
void initState() {
super.initState();
_scrollController = AutoScrollController(
viewportBoundaryGetter: () => Rect.fromLTRB(
0,
0,
0,
MediaQuery.of(context).padding.bottom,
),
axis: Axis.vertical,
);
_loadMessages();
}
Future<void> _loadMessages() async {
TimMessageService.to
.loadMessagesFromService(
widget.conversation,
_lastMessageId,
_scrollController,
)
.then((value) {
_lastMessageId = value;
});
}
@override
Widget build(BuildContext context) {
return GetX<TimMessageService>(builder: (service) {
return ListView.separated(
scrollDirection: Axis.vertical,
controller: _scrollController,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.all(8),
reverse: true,
cacheExtent: 1200,
addAutomaticKeepAlives: true,
itemCount: service.messages.length,
itemBuilder: (_, index) {
V2TimMessage? message = service.messages[index];
return AutoScrollTag(
key: ValueKey(index),
controller: _scrollController,
index: index,
child: MessageWidget(message),
);
},
separatorBuilder: (c, i) {
return Container(
height: 8,
);
},
);
});
}
}

View File

@@ -0,0 +1,676 @@
import 'dart:async';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/services/tim/conversation_service.dart';
import 'package:chat/utils/sound_record.dart';
import 'package:chat/utils/ui_tools.dart';
import 'package:chat/views/conversation/widgets/tim_emoji_panel.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
// import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';
class MessageField extends StatefulWidget {
final V2TimConversation conversation;
const MessageField(this.conversation, {Key? key}) : super(key: key);
@override
State<MessageField> createState() => _MessageFieldState();
}
class _MessageFieldState extends State<MessageField> {
final TextEditingController _controller = TextEditingController();
bool isVoice = false;
String _sendText = '';
double paddingBottom = 0.0;
String soundTipsText = "手指上滑,取消发送";
bool isRecording = false;
bool isInit = false;
bool isCancelSend = false;
DateTime startTime = DateTime.now();
List<StreamSubscription<Object>> subscriptions = [];
OverlayEntry? overlayEntry;
String voiceIcon = "assets/chats/voice_volume_1.png";
final FocusNode _focusNode = FocusNode();
bool showMore = false;
bool showEmojiPanel = false;
bool showKeyboard = false;
double lastkeyboardHeight = 0;
@override
void dispose() {
_controller.dispose();
for (var subscription in subscriptions) {
subscription.cancel();
}
super.dispose();
}
Future<void> sendMessage() async {
var text = _controller.text;
if (text.isEmpty) {
return;
}
TimConversationService.to.sendTextMessage(widget.conversation, text);
_controller.text = '';
}
/// 发送媒体消息
Future<void> sendMeidaMessage(AssetEntity asset) async {
if (asset.type == AssetType.image) {
TimConversationService.to.sendImageMessage(
widget.conversation,
asset,
);
} else if (asset.type == AssetType.video) {
TimConversationService.to.sendVideoMessage(
widget.conversation,
asset,
);
} else {
UiTools.toast('暂不支持的类型');
}
}
hideAllPanel() {
_focusNode.unfocus();
setState(() {
showKeyboard = false;
showMore = false;
showEmojiPanel = false;
});
}
double _getBottomHeight() {
listenKeyBoardStatus();
if (showMore || showEmojiPanel) {
return 248.0;
}
// 在文本框多行拓展时增加保护区高度
else if (_controller.text.length >= 46 && showKeyboard == false) {
return 25;
} else {
return 0;
}
}
listenKeyBoardStatus() {
final currentKeyboardHeight = MediaQuery.of(context).viewInsets.bottom;
// 键盘弹出
if (currentKeyboardHeight - lastkeyboardHeight > 0) {
// 保证弹出时showKeyboard为true
setState(() {
showKeyboard = true;
});
/// 键盘收回
} else if (currentKeyboardHeight - lastkeyboardHeight < 0) {}
lastkeyboardHeight = MediaQuery.of(context).viewInsets.bottom;
}
_openMore() {
if (showMore) {
_focusNode.requestFocus();
} else {
_focusNode.unfocus();
}
setState(() {
showKeyboard = showMore;
showEmojiPanel = false;
showMore = !showMore;
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(height: 0),
Container(
padding: EdgeInsets.only(
left: 8,
right: 8,
top: 8,
bottom: 8 + Get.mediaQuery.viewInsets.bottom,
),
child: Row(
children: [
_voiceButton(),
const SizedBox(width: 8),
Expanded(child: _inputArea()),
const SizedBox(width: 8),
_emojiButton(),
const SizedBox(width: 8),
_sendButton()
],
),
),
const Divider(height: 0),
AnimatedContainer(
height: _getBottomHeight(),
duration: const Duration(milliseconds: 150),
padding: showEmojiPanel
? const EdgeInsets.all(0)
: const EdgeInsets.all(16),
child: _getBottomContainer(),
),
],
),
);
}
Widget _actionWidget(
String text, {
required IconData icon,
VoidCallback? onTap,
}) {
return InkWell(
onTap: () {
onTap?.call();
},
child: Column(
children: [
Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(5),
),
child: Icon(
icon,
color: AppColors.unactive,
),
),
const SizedBox(height: 3),
Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
],
),
);
}
_openEmojiPanel() {
if (showEmojiPanel) {
_focusNode.requestFocus();
} else {
_focusNode.unfocus();
}
setState(() {
showKeyboard = showEmojiPanel;
showMore = false;
showEmojiPanel = !showEmojiPanel;
});
}
Widget _emojiButton() {
return InkWell(
onTap: () {
_openEmojiPanel();
},
child: const Icon(
Icons.emoji_emotions_outlined,
size: 28,
),
);
}
Widget _voiceButton() {
if (!isVoice) {
return InkWell(
onTap: () {
setState(() {
isVoice = true;
});
},
child: const Icon(
Icons.mic_outlined,
size: 28,
),
);
} else {
return InkWell(
onTap: () {
setState(() {
isVoice = false;
});
},
child: const Icon(
Icons.keyboard_outlined,
size: 28,
),
);
}
}
Widget _inputArea() {
if (isVoice) {
return GestureDetector(
onTapDown: (_) async {
if (!isInit) {
var result = await Permission.microphone.request().isGranted;
if (result) initRecordSound();
}
},
onLongPressStart: onLongPressStart,
onLongPressMoveUpdate: onLongPressUpdate,
onLongPressEnd: onLongPressEnd,
onLongPressCancel: onLonePressCancel,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
top: 8.0,
bottom: 8.0,
),
decoration: const BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.all(
Radius.circular(
4.0,
),
),
),
child: const Text('按住说话'),
),
);
} else {
return Container(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
top: 8.0,
bottom: 8.0,
),
decoration: const BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.all(
Radius.circular(
4.0,
),
),
),
child: TextField(
controller: _controller,
focusNode: _focusNode,
decoration: null,
onChanged: (e) {
setState(() {
_sendText = e;
});
},
onTap: () {
showKeyboard = true;
showMore = false;
showEmojiPanel = false;
},
onSubmitted: (value) {
sendMessage();
},
),
);
}
}
Widget _sendButton() {
if (_sendText.isEmpty) {
return InkWell(
onTap: () {
_openMore();
},
child: const Icon(
Icons.add_circle_outline_outlined,
size: 28,
color: AppColors.active,
),
);
} else {
return InkWell(
onTap: () {
sendMessage();
},
child: Container(
height: 28.0,
width: 54.0,
alignment: Alignment.center,
decoration: const BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.all(
Radius.circular(
8.0,
),
),
),
child: const Text(
'发送',
style: TextStyle(
letterSpacing: 2,
color: AppColors.white,
fontSize: 14.0,
),
),
),
);
}
}
initRecordSound() {
final responseSubscription = SoundPlayer.responseListener((recordResponse) {
final status = recordResponse.msg;
if (status == "onStop") {
if (!isCancelSend) {
final soundPath = recordResponse.path;
final recordDuration = recordResponse.audioTimeLength;
TimConversationService.to.sendSoundMessage(
widget.conversation, soundPath!, recordDuration!.toInt());
}
} else if (status == "onStart") {
// print("start record");
setState(() {
isRecording = true;
});
} else {
// print(status);
}
});
final amplitutdeResponseSubscription =
SoundPlayer.responseFromAmplitudeListener((recordResponse) {
final voiceData = double.parse(recordResponse.msg!);
setState(() {
if (voiceData > 0 && voiceData < 0.1) {
voiceIcon = "assets/chats/voice_volume_2.png";
} else if (voiceData > 0.2 && voiceData < 0.3) {
voiceIcon = "assets/chats/voice_volume_3.png";
} else if (voiceData > 0.3 && voiceData < 0.4) {
voiceIcon = "assets/chats/voice_volume_4.png";
} else if (voiceData > 0.4 && voiceData < 0.5) {
voiceIcon = "assets/chats/voice_volume_5.png";
} else if (voiceData > 0.5 && voiceData < 0.6) {
voiceIcon = "assets/chats/voice_volume_6.png";
} else if (voiceData > 0.6 && voiceData < 0.7) {
voiceIcon = "assets/chats/voice_volume_7.png";
} else if (voiceData > 0.7 && voiceData < 1) {
voiceIcon = "assets/chats/voice_volume_7.png";
} else {
voiceIcon = "assets/chats/voice_volume_1.png";
}
if (overlayEntry != null) {
overlayEntry!.markNeedsBuild();
}
});
});
subscriptions = [responseSubscription, amplitutdeResponseSubscription];
SoundPlayer.initSoundPlayer();
isInit = true;
}
buildOverLayView(BuildContext context) {
if (overlayEntry == null) {
overlayEntry = OverlayEntry(builder: (content) {
return Positioned(
top: MediaQuery.of(context).size.height * 0.5 - 80,
left: MediaQuery.of(context).size.width * 0.5 - 80,
child: Material(
type: MaterialType.transparency,
child: Center(
child: Opacity(
opacity: 0.8,
child: Container(
width: 160,
height: 160,
decoration: const BoxDecoration(
color: Color(0xff77797A),
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
child: Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 10),
child: Image.asset(
voiceIcon,
width: 100,
height: 100,
// package: 'flutter_plugin_record',
),
),
Text(
soundTipsText,
style: const TextStyle(
fontStyle: FontStyle.normal,
color: Colors.white,
fontSize: 14,
),
)
],
),
),
),
),
),
);
});
Overlay.of(context)!.insert(overlayEntry!);
}
}
onLongPressStart(_) {
if (isInit) {
startTime = DateTime.now();
SoundPlayer.startRecord();
buildOverLayView(context);
}
}
onLongPressUpdate(e) {
double height = MediaQuery.of(context).size.height * 0.5 - 240;
double dy = e.localPosition.dy;
if (dy.abs() > height) {
if (mounted && soundTipsText != '松开取消') {
setState(() {
soundTipsText = '松开取消';
});
}
} else {
if (mounted && soundTipsText == '松开取消') {
setState(() {
soundTipsText = '手指上滑,取消发送';
});
}
}
}
onLongPressEnd(e) {
double dy = e.localPosition.dy;
// 此高度为 160为录音取消组件距离顶部的预留距离
double height = MediaQuery.of(context).size.height * 0.5 - 240;
if (dy.abs() > height) {
isCancelSend = true;
} else {
isCancelSend = false;
}
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
// Did not receive onStop from FlutterPluginRecord if the duration is too short.
if (DateTime.now().difference(startTime).inSeconds < 1) {
isCancelSend = true;
UiTools.toast('说话时间太短!');
}
stop();
}
onLonePressCancel() {
if (isRecording) {
isCancelSend = true;
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
stop();
}
}
void stop() {
setState(() {
isRecording = false;
});
SoundPlayer.stopRecord();
setState(() {
soundTipsText = '手指上滑,取消发送';
});
}
_getMoreActions() {
var list = List<Widget>.empty(growable: true);
list.add(
_actionWidget('相册', icon: Icons.photo, onTap: () async {
var result = await AssetPicker.pickAssets(
context,
pickerConfig: const AssetPickerConfig(
maxAssets: 9,
requestType: RequestType.common,
),
);
if (result == null) {
return;
}
for (var asset in result) {
sendMeidaMessage(asset);
}
}),
);
list.add(
_actionWidget('拍照', icon: Icons.photo_camera, onTap: () async {
var asset = await CameraPicker.pickFromCamera(
context,
pickerConfig: const CameraPickerConfig(
enableRecording: true,
),
);
if (asset == null) {
return;
}
sendMeidaMessage(asset);
}),
);
if (widget.conversation.type == ConversationType.V2TIM_C2C) {
list.add(
_actionWidget(
'视频通话',
icon: Icons.videocam,
onTap: () {
// ImTools.showTrtcMessage(widget.conversation.userID!);
},
),
);
}
list.add(
_actionWidget(
'位置',
icon: Icons.pin_drop,
onTap: () {
// Get.toNamed(ImRoutes.conversationMap)?.then((value) {
// var _bmfPoiInfo = value['result'] as BMFPoiInfo;
// var snapshot = value['snapshot'] as Uint8List?;
// var model = LocationModel(
// name: _bmfPoiInfo.name!,
// address: _bmfPoiInfo.address!,
// list: snapshot!,
// latitude: _bmfPoiInfo.pt?.latitude ?? 0,
// longitude: _bmfPoiInfo.pt?.longitude ?? 0,
// );
// TimConversationService.to.sendLocationMessage(
// widget.conversation,
// model,
// );
// });
},
),
);
list.add(
_actionWidget(
'名片',
icon: Icons.person,
onTap: () {
Get.toNamed(
ContactRoutes.friend,
arguments: {
'name_card': true,
},
)?.then((value) {
var model = value?['result'];
if (model != null) {
TimConversationService.to.sendCustomMessage(
widget.conversation,
model,
'NAME_CARD',
);
}
});
},
),
);
list.add(
_actionWidget(
'文件',
icon: Icons.folder,
onTap: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
TimConversationService.to.sendFileMessage(
widget.conversation,
result.names.first!,
result.paths.first!,
);
}
},
),
);
return list;
}
Widget _getBottomContainer() {
if (showEmojiPanel) {
// eventBus.fire('scrollToBottom');
return EmojiPanel(
onTapEmoji: (unicode) {
final oldText = _controller.text;
final newText = String.fromCharCode(unicode);
_controller.text = "$oldText$newText";
setState(() {
_sendText = _controller.text;
});
},
);
}
if (showMore) {
// eventBus.fire('scrollToBottom');
return GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 4,
children: _getMoreActions(),
);
}
return Container();
}
}

View File

@@ -0,0 +1,126 @@
import 'package:chat/services/tim/message_service.dart';
import 'package:chat/widgets/custom_easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get/get.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
import 'message_widget.dart';
class MessageList extends StatefulWidget {
final V2TimConversation conversation;
const MessageList(this.conversation, {Key? key}) : super(key: key);
@override
State<MessageList> createState() => _MessageListState();
}
class _MessageListState extends State<MessageList> {
final EasyRefreshController _refreshController = EasyRefreshController();
late AutoScrollController _scrollController;
String? _lastMessageId;
bool isFirst = true;
@override
void dispose() {
super.dispose();
}
@override
void initState() {
super.initState();
_scrollController = AutoScrollController(
viewportBoundaryGetter: () => Rect.fromLTRB(
0,
0,
0,
MediaQuery.of(context).padding.bottom,
),
axis: Axis.vertical,
);
/// 接收到消息 和 自己发送消息 使列表滚动到最底部
// eventBus.on().listen((event) {
// if (TimMessageService.to.curConversationId ==
// widget.conversation.conversationID) {
// if (event is V2TimMessage) {
// if (mounted) {
// setState(() {
// TimMessageService.to.addMessage(event);
// });
// }
// _scrollController.scrollToIndex(
// TimMessageService.to.messages.length - 1,
// preferPosition: AutoScrollPosition.begin,
// );
// }
// }
// if (event is String && event == 'scrollToBottom') {
// Future.delayed(const Duration(milliseconds: 200), () {
// _scrollController.animateTo(
// _scrollController.position.maxScrollExtent,
// duration: const Duration(milliseconds: 200),
// curve: Curves.easeOut,
// );
// });
// }
// });
_loadMessages();
}
Future<void> _loadMessages() async {
TimMessageService.to
.loadMessagesFromService(
widget.conversation,
_lastMessageId,
_scrollController,
)
.then((value) {
_lastMessageId = value;
});
}
@override
Widget build(BuildContext context) {
return GetX<TimMessageService>(builder: (service) {
return EasyRefresh(
controller: _refreshController,
header: CustomEasyRefresh.header,
footer: CustomEasyRefresh.footer,
firstRefresh: false,
onRefresh: _loadMessages,
child: ListView.separated(
scrollDirection: Axis.vertical,
controller: _scrollController,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(
left: 8,
right: 8,
top: 8,
bottom: 8,
),
reverse: false,
itemCount: service.messages.length,
itemBuilder: (_, index) {
V2TimMessage? message = service.messages[index];
return AutoScrollTag(
key: ValueKey(index),
controller: _scrollController,
index: index,
child: MessageWidget(message),
);
},
separatorBuilder: (c, i) {
return Container(
height: 8,
);
},
),
);
});
}
}

View File

@@ -0,0 +1,274 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/routes/user_routes.dart';
import 'package:chat/services/auth_service.dart';
import 'package:chat/views/conversation/widgets/show_custom_message.dart';
import 'package:chat/views/conversation/widgets/show_face_message.dart';
import 'package:chat/views/conversation/widgets/show_file_message.dart';
import 'package:chat/views/conversation/widgets/show_image_message.dart';
import 'package:chat/views/conversation/widgets/show_location_message.dart';
import 'package:chat/views/conversation/widgets/show_merger_message.dart';
import 'package:chat/views/conversation/widgets/show_sound_message.dart';
import 'package:chat/views/conversation/widgets/show_text_message.dart';
import 'package:chat/views/conversation/widgets/show_video_message.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/enum/group_change_info_type.dart';
import 'package:tencent_im_sdk_plugin/enum/group_tips_elem_type.dart';
import 'package:tencent_im_sdk_plugin/enum/message_elem_type.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class MessageWidget extends StatelessWidget {
final V2TimMessage message;
const MessageWidget(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
switch (message.elemType) {
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
return _buildMessage(ShowTextMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
return _buildMessage(ShowImageMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
return _buildMessage(ShowSoundMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return _buildMessage(ShowVideoMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
return _buildMessage(ShowFileMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return _buildMessage(ShowLocationMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return _buildMessage(ShowFaceMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return _buildMessage(ShowMergerMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return _buildMessage(ShowCustomMessage(message));
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return _buildGroupTipMessage();
default:
return Text('未识别的消息类型 ${message.elemType}');
}
}
/// 构造群提示消息
Widget _buildGroupTipMessage() {
switch (message.groupTipsElem!.type) {
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE:
return _buildGroupInfoChangeMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_JOIN:
return _buildGroupTypeJoinMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_INVITE:
return _buildGroupTypeInviteMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_QUIT:
return _buildGroupTypeQuitMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_KICKED:
return _buildGroupTypeKickedMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_SET_ADMIN:
return _buildGroupTypeSetAdminMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN:
return _buildGroupTypeCancelAdminMessage();
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_MEMBER_INFO_CHANGE:
return _buildGroupTypeMemberInfoChangeMessage();
default:
return Text(message.groupTipsElem!.type.toString());
}
}
Widget _buildGroupTypeMemberInfoChangeMessage() {
return Center(
child: Text(
'${message.groupTipsElem!.opMember.nickName}修改了群成员资料',
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
Widget _buildGroupTypeCancelAdminMessage() {
String op = message.groupTipsElem!.opMember.nickName ?? '';
String text = '';
for (var e in message.groupTipsElem!.memberList!) {
text += e?.nickName ?? '\t';
}
text = '$op取消了$text的管理员身份';
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
Widget _buildGroupTypeSetAdminMessage() {
String op = message.groupTipsElem!.opMember.nickName ?? '';
String text = '';
for (var e in message.groupTipsElem!.memberList!) {
text += e?.nickName ?? '\t';
}
text = '$op将$text设置为管理员';
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
Widget _buildGroupTypeKickedMessage() {
String op = message.groupTipsElem!.opMember.nickName ?? '';
String text = '';
for (var e in message.groupTipsElem!.memberList!) {
text += e?.nickName ?? '\t';
}
text = '$op将$text踢出群组';
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
/// 退出群组的消息解析
Widget _buildGroupTypeQuitMessage() {
if (message.groupTipsElem!.memberList!.isEmpty) {
return Container();
}
return Center(
child: Text(
'${message.groupTipsElem!.memberList!.first?.nickName}退出了群组',
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
Widget _buildGroupTypeInviteMessage() {
String op = message.groupTipsElem!.opMember.nickName ?? '';
String text = '';
for (var e in message.groupTipsElem!.memberList!) {
text += e?.nickName ?? '\t';
}
text = '$op邀请$text加入群组';
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
Widget _buildGroupTypeJoinMessage() {
String text = '';
for (var e in message.groupTipsElem!.memberList!) {
text += e?.nickName ?? '\t';
}
text = '$text加入群组';
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
/// 解析群资料修改的内容
Widget _buildGroupInfoChangeMessage() {
String text = '';
switch (message.groupTipsElem!.groupChangeInfoList!.first!.type) {
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NAME:
text =
'${message.groupTipsElem!.opMember.nickName} 修改群名称 ${message.groupTipsElem!.groupChangeInfoList!.first!.value}';
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_INTRODUCTION:
text = '群简介修改';
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NOTIFICATION:
text = '${message.groupTipsElem!.opMember.nickName} 更新了群公告';
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_FACE_URL:
text = '群头像修改';
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER:
text = '${message.groupTipsElem!.memberList!.first!.nickName} 成为新群主';
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_CUSTOM:
text = '群自定义字段变更';
break;
default:
text = message.groupTipsElem!.groupChangeInfoList![0]!.type.toString();
break;
}
return Center(
child: Text(
text,
style: const TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
);
}
/// 构造普通内容的消息
Widget _buildMessage(Widget child) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: message.isSelf! ? TextDirection.rtl : TextDirection.ltr,
children: [
InkWell(
onTap: () {
if (message.isSelf!) {
Get.toNamed(UserRoutes.info);
} else {
Get.toNamed(
ContactRoutes.friendProfile,
);
}
},
child: message.isSelf!
? GetX<AuthService>(builder: (_) {
return CustomAvatar(
_.userInfo.value.avatar ?? '',
size: 43,
radius: 4,
);
})
: CustomAvatar(
message.faceUrl,
size: 43,
radius: 4,
),
),
const SizedBox(width: 8),
GestureDetector(
onLongPress: () {},
child: child,
),
],
);
}
}

View File

@@ -0,0 +1,61 @@
import 'dart:convert';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/models/im/calling_model.dart';
import 'package:chat/models/im/custom_message_model.dart';
import 'package:flutter/material.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowCallMessage extends StatefulWidget {
final V2TimMessage message;
const ShowCallMessage(this.message, {Key? key}) : super(key: key);
@override
State<ShowCallMessage> createState() => _ShowCallMessageState();
}
class _ShowCallMessageState extends State<ShowCallMessage> {
@override
Widget build(BuildContext context) {
var model = CallingModel.fromJson(
json.decode(widget.message.customElem!.data!),
);
final isVoiceCall = model.callType == CallingType.audioCall;
return Container(
constraints: const BoxConstraints(minHeight: 43),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: widget.message.isSelf! ? AppColors.primary : AppColors.white,
),
child: Row(
children: [
Icon(
isVoiceCall ? Icons.call_end : Icons.videocam,
size: 20,
color: widget.message.isSelf! ? AppColors.white : AppColors.active,
),
const SizedBox(width: 4),
model.timeout > 0
? Text(
'通话时长',
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.active,
),
)
: Text(
model.actionTypeText,
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.active,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,61 @@
import 'dart:convert';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/models/im/custom_message_model.dart';
import 'package:chat/views/conversation/widgets/show_call_message.dart';
import 'package:chat/views/conversation/widgets/show_group_card_message.dart';
import 'package:chat/views/conversation/widgets/show_name_card_message.dart';
import 'package:chat/views/conversation/widgets/show_transfer_message.dart';
import 'package:flutter/material.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowCustomMessage extends StatelessWidget {
final V2TimMessage message;
const ShowCustomMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
try {
String type =
json.decode(message.customElem!.data!)['businessID'].toString();
switch (type) {
// case CustomMessageType.EVALUATION:
// return ShowEvaluationMessage(message);
case CustomMessageType.NAME_CARD:
return ShowNameCardMessage(message);
case CustomMessageType.DT_TRANSFER:
return ShowTransferMessage(message);
case CustomMessageType.CALL:
return ShowCallMessage(message);
case CustomMessageType.GROUP_CARD:
return ShowGroupCardMessage(message);
default:
return _unknowMessage();
}
} catch (e) {
return _unknowMessage();
}
}
Widget _unknowMessage() {
return Container(
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(4),
),
alignment: Alignment.center,
child: Column(
children: [
const Text('【自定义消息】'),
Text(
message.customElem!.data!,
maxLines: 5,
),
],
),
);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowFaceMessage extends StatelessWidget {
final V2TimMessage message;
const ShowFaceMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@@ -0,0 +1,70 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowFileMessage extends StatelessWidget {
final V2TimMessage message;
const ShowFileMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
// try {
// var result = await OpenFile.open(message.fileElem!.localUrl);
// if (result.type != ResultType.done) {
// UiTools.toast(result.message);
// }
// } catch (e) {
// UiTools.toast(e.toString());
// }
},
child: Container(
padding: const EdgeInsets.all(12),
width: Get.width * 0.618,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: AppColors.white,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.fileElem!.fileName!,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: AppSize.fontSize,
color: AppColors.active,
),
),
const SizedBox(height: 8),
Text(
filesize(message.fileElem!.fileSize),
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: AppSize.smallFontSize,
color: AppColors.unactive,
),
),
],
),
),
const SizedBox(width: 8),
Image.asset(
'assets/chats/file_msg.png',
width: 44,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,59 @@
import 'dart:convert';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:chat/models/im/group_card_model.dart';
import 'package:chat/views/home/widgets/group_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowGroupCardMessage extends StatelessWidget {
final V2TimMessage message;
const ShowGroupCardMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var model = GroupCardModel.fromJson(
json.decode(message.customElem!.data!),
);
return Container(
width: Get.width * 0.618,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: AppColors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GroupAvatar(model.groupID),
const SizedBox(
width: 8,
),
Text(
model.groupName,
style: const TextStyle(
color: AppColors.primary,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
)
],
),
const Divider(height: 16),
const Text(
'个人名片',
style: TextStyle(
fontSize: AppSize.smallFontSize,
color: AppColors.primary,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,123 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/constants/message_constant.dart';
import 'package:chat/views/conversation/preview/image_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_image.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowImageMessage extends StatelessWidget {
final V2TimMessage message;
const ShowImageMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
constraints: BoxConstraints(
maxWidth: Get.width * 0.362,
minWidth: 64,
maxHeight: 192,
),
clipBehavior: Clip.hardEdge,
child: _imageBuilder(),
);
}
Widget _imageBuilder() {
var path = message.imageElem!.path!;
var list = message.imageElem!.imageList!;
V2TimImage? small = list.where((e) => e!.type == 2).first;
V2TimImage? big = list.where((e) => e!.type == 1).first;
V2TimImage? original = list.where((e) => e!.type == 0).first;
/// 如果是从本机发出去的图片消息,并且图片还存在的情况
if (path.isNotEmpty && File(path).existsSync()) {
return _showLocalImageFile(path, original!.localUrl!);
} else if (small != null && File(small.localUrl!).existsSync()) {
return _showLocalImageFile(small.localUrl!, original!.localUrl!);
} else if (big != null && File(big.localUrl!).existsSync()) {
return _showLocalImageFile(big.localUrl!, original!.localUrl!);
} else if (original != null && File(original.localUrl!).existsSync()) {
return _showLocalImageFile(original.localUrl!, original.localUrl!);
} else {
return GestureDetector(
onTap: () {
Get.to(
PreviewImageWidget(
type: IMG_PREVIEW_TYPE.url,
path: big!.url!,
original: original!.url!,
),
transition: Transition.zoom,
);
},
child: CachedNetworkImage(
imageUrl: small!.url!,
cacheKey: 'CHAT_IMAGE',
alignment: Alignment.topCenter,
errorWidget: (context, error, stackTrace) => _errorDisplay(),
),
);
}
}
Widget _showLocalImageFile(String path, String original) {
return GestureDetector(
onTap: () {
Get.to(
PreviewImageWidget(
type: IMG_PREVIEW_TYPE.local,
path: path,
original: original,
),
transition: Transition.zoom,
);
},
child: Image.file(
File(path),
fit: BoxFit.fitWidth,
),
);
}
Widget _errorDisplay() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 0.4,
color: AppColors.unactive,
),
),
height: 96,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.warning_amber_outlined,
size: 20,
color: AppColors.unactive,
),
SizedBox(width: 4),
Text(
'图片加载失败',
style: TextStyle(
color: AppColors.unactive,
fontSize: 12,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,283 @@
// import 'dart:io';
// import 'dart:math';
// import 'package:cached_network_image/cached_network_image.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:scaffold/constants/message_constant.dart';
// import 'package:scaffold/utils/ui_tools.dart';
// import 'package:scaffold/views/im/conversation/preview/index_page.dart';
// import 'package:scaffold/views/moments/index/widgets/media_preview.dart';
// import 'package:tencent_im_sdk_plugin/models/v2_tim_image.dart';
// import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
// import 'package:transparent_image/transparent_image.dart';
// class ShowImageMessage extends StatefulWidget {
// final V2TimMessage message;
// const ShowImageMessage(this.message, {Key? key}) : super(key: key);
// @override
// State<ShowImageMessage> createState() => _ShowImageMessageState();
// }
// class _ShowImageMessageState extends State<ShowImageMessage> {
// bool imageIsRender = false;
// @override
// didUpdateWidget(oldWidget) {
// var oldImgListLength = oldWidget.message.imageElem?.imageList?.length ?? 0;
// var currImgListLength = widget.message.imageElem?.imageList?.length ?? 0;
// if (currImgListLength == 1 && oldImgListLength == 0) {
// setState(() {
// imageIsRender = true;
// });
// }
// super.didUpdateWidget(oldWidget);
// }
// @override
// Widget build(BuildContext context) {
// V2TimImage? originalImg =
// getImageFromList(V2_TIM_IMAGE_TYPES_ENUM.original);
// V2TimImage? smallImg = getImageFromList(V2_TIM_IMAGE_TYPES_ENUM.small);
// return Container(
// decoration: BoxDecoration(
// borderRadius: const BorderRadius.all(Radius.circular(4)),
// border: Border.all(
// color: const Color.fromRGBO(245, 166, 35, 0),
// width: 2,
// ),
// ),
// child: LayoutBuilder(
// builder: (BuildContext context, BoxConstraints constraints) {
// return ConstrainedBox(
// constraints: BoxConstraints(
// maxWidth: constraints.maxWidth * 0.5,
// minWidth: 64,
// maxHeight: 256,
// ),
// child: imageBuilder(
// originalImg: originalImg,
// smallImg: smallImg,
// ),
// );
// },
// ),
// );
// }
// Widget imageBuilder({V2TimImage? originalImg, V2TimImage? smallImg}) {
// if (originalImg == null) {
// // 有path
// if (widget.message.imageElem!.path!.isNotEmpty &&
// File(widget.message.imageElem!.path!).existsSync()) {
// return getImage(
// GestureDetector(
// onTap: () {
// Get.to(ImagePreviewPage(
// type: IMG_PREVIEW_TYPE.local,
// path: widget.message.imageElem!.path!,
// ));
// },
// child: Image.file(
// File(widget.message.imageElem!.path!),
// fit: BoxFit.fitWidth,
// ),
// ),
// imageElem: null,
// );
// } else {
// return errorDisplay();
// }
// } else if (!Platform.isAndroid &&
// widget.message.imageElem!.path!.isNotEmpty &&
// File(widget.message.imageElem!.path!).existsSync() &&
// !imageIsRender) {
// return getImage(
// GestureDetector(
// onTap: () {
// Get.to(ImagePreviewPage(
// type: IMG_PREVIEW_TYPE.local,
// path: widget.message.imageElem!.path!,
// ));
// },
// child: Image.file(
// File(widget.message.imageElem!.path!),
// fit: BoxFit.fitWidth,
// ),
// ),
// imageElem: e,
// );
// } else if ((smallImg?.url ?? originalImg.url) != null && !imageIsRender) {
// double positionRadio = 1.0;
// if (smallImg?.width != null &&
// smallImg?.height != null &&
// smallImg?.width != 0 &&
// smallImg?.height != 0) {
// positionRadio = (smallImg!.width! / smallImg.height!);
// }
// String bigImgUrl = originalImg.url ?? getBigPicUrl();
// if (bigImgUrl.isEmpty && smallImg?.url != null) {
// bigImgUrl = smallImg!.url!;
// }
// return Stack(
// alignment: AlignmentDirectional.topStart,
// children: [
// AspectRatio(
// aspectRatio: positionRadio,
// child: Container(
// decoration: const BoxDecoration(color: Colors.white),
// ),
// ),
// getImage(
// GestureDetector(
// onTap: () {
// Get.to(ImagePreviewPage(
// type: IMG_PREVIEW_TYPE.url,
// path: smallImg?.url ?? originalImg.url!,
// ));
// },
// child: CachedNetworkImage(
// alignment: Alignment.topCenter,
// imageUrl: smallImg?.url ?? originalImg.url!,
// errorWidget: (context, error, stackTrace) => errorDisplay(),
// fit: BoxFit.fitWidth,
// cacheKey: smallImg?.uuid ?? originalImg.uuid!,
// placeholder: (context, url) =>
// Image(image: MemoryImage(kTransparentImage)),
// fadeInDuration: const Duration(milliseconds: 0),
// ),
// ),
// imageElem: e,
// ),
// ],
// );
// } else {
// // 有path
// if (widget.message.imageElem!.path!.isNotEmpty &&
// File(widget.message.imageElem!.path!).existsSync()) {
// return getImage(
// GestureDetector(
// onTap: () {
// Get.to(ImagePreviewPage(
// type: IMG_PREVIEW_TYPE.local,
// path: widget.message.imageElem!.path!,
// ));
// },
// child: Image.file(
// File(widget.message.imageElem!.path!),
// fit: BoxFit.fitWidth,
// ),
// ),
// imageElem: null,
// );
// } else {
// return errorDisplay();
// }
// }
// }
// Widget errorDisplay() {
// return Container(
// decoration: BoxDecoration(
// borderRadius: const BorderRadius.all(Radius.circular(5)),
// border: Border.all(
// width: 1,
// color: Colors.black12,
// ),
// ),
// height: 100,
// child: Center(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: const [
// Icon(
// Icons.warning_amber_outlined,
// size: 16,
// ),
// Text(
// '图片加载失败',
// ),
// ],
// ),
// ),
// );
// }
// void pushToPreview(List<String> mediaList) {
// Get.dialog(
// MomentMediaPreview(
// mediaSourceList: mediaList,
// initialPage: 0,
// ),
// useSafeArea: false,
// );
// }
// Widget getImage(image, {imageElem}) {
// Widget res = ClipRRect(
// clipper: ImageClipper(),
// child: image,
// );
// return res;
// }
// V2TimImage? getImageFromList(V2_TIM_IMAGE_TYPES_ENUM imgType) {
// V2TimImage? img = getImageFromImgList(
// widget.message.imageElem!.imageList,
// HistoryMessageDartConstant.imgPriorMap[imgType] ??
// HistoryMessageDartConstant.oriImgPrior,
// );
// return img;
// }
// String getBigPicUrl() {
// // 实际拿的是原图
// V2TimImage? img = getImageFromImgList(
// widget.message.imageElem!.imageList,
// HistoryMessageDartConstant.oriImgPrior,
// );
// if (img == null) {
// setState(() {
// imageIsRender = true;
// });
// }
// return img == null ? widget.message.imageElem!.path! : img.url!;
// }
// V2TimImage? getImageFromImgList(List<V2TimImage?>? list, List<String> order) {
// V2TimImage? img;
// try {
// for (String type in order) {
// img = list?.firstWhere(
// (e) => e?.type == HistoryMessageDartConstant.V2_TIM_IMAGE_TYPES[type],
// orElse: () => null,
// );
// }
// } catch (e) {
// UiTools.toast('getImageFromImgList error ${e.toString()}');
// }
// return img;
// }
// }
// class ImageClipper extends CustomClipper<RRect> {
// @override
// RRect getClip(Size size) {
// return RRect.fromRectAndRadius(
// Rect.fromLTWH(
// 0,
// 0,
// size.width,
// min(size.height, 256),
// ),
// const Radius.circular(5),
// );
// }
// @override
// bool shouldReclip(CustomClipper<RRect> oldClipper) {
// return oldClipper != this;
// }
// }

View File

@@ -0,0 +1,101 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowLocationMessage extends StatelessWidget {
final V2TimMessage message;
const ShowLocationMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Permission.location.request().isGranted.then((value) {
if (value) {
// Get.toNamed(
// ConversationRoutes.conversationMapShow,
// arguments: {
// 'message': message,
// },
// );
}
});
},
child: Container(
width: Get.width * 0.618,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: message.isSelf! ? AppColors.primary : AppColors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.pin_drop_outlined,
size: 44,
color: message.isSelf! ? AppColors.white : AppColors.primary,
),
const SizedBox(
width: 8,
),
Expanded(
child: Text(
message.locationElem!.desc!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color:
message.isSelf! ? AppColors.white : AppColors.primary,
fontSize: AppSize.fontSize,
fontWeight: FontWeight.bold,
),
),
),
],
),
Divider(
height: 16,
color: message.isSelf! ? AppColors.white : null,
),
Text(
'位置消息',
style: TextStyle(
color: message.isSelf! ? AppColors.white : AppColors.primary,
fontSize: AppSize.smallFontSize,
),
),
],
),
),
// child: Container(
// padding: const EdgeInsets.all(12),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// color: message.isSelf! ? AppColors.primary : AppColors.white,
// ),
// child: Row(
// children: [
// Icon(
// Icons.pin_drop_outlined,
// size: 20,
// color: message.isSelf! ? AppColors.white : AppColors.active,
// ),
// Text(
// '【位置消息】点击查看',
// style: TextStyle(
// fontSize: AppSize.fontSize,
// color: message.isSelf! ? AppColors.white : AppColors.active,
// ),
// ),
// ],
// ),
// ),
);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowMergerMessage extends StatelessWidget {
final V2TimMessage message;
const ShowMergerMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@@ -0,0 +1,76 @@
import 'dart:convert';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:chat/controllers/private_controller.dart';
import 'package:chat/models/im/name_card_model.dart';
import 'package:chat/routes/contact_routes.dart';
import 'package:chat/widgets/custom_avatar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
/// 个人名片消息
class ShowNameCardMessage extends StatelessWidget {
final V2TimMessage message;
const ShowNameCardMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var model = NameCardModel.fromJson(
json.decode(message.customElem!.data!),
);
return InkWell(
onTap: () async {
await PrivateController.to.setCurrentFriend(
model.userID,
);
Get.toNamed(
ContactRoutes.friendProfile,
);
},
child: Container(
width: Get.width * 0.618,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: AppColors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomAvatar(
model.avatar,
size: 44,
),
const SizedBox(
width: 8,
),
Text(
model.userName,
style: const TextStyle(
color: AppColors.primary,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
)
],
),
const Divider(height: 16),
const Text(
'个人名片',
style: TextStyle(
fontSize: AppSize.smallFontSize,
color: AppColors.unactive,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,140 @@
import 'dart:io';
import 'dart:math';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:chat/utils/sound_record.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowSoundMessage extends StatefulWidget {
final V2TimMessage message;
const ShowSoundMessage(this.message, {Key? key}) : super(key: key);
@override
State<ShowSoundMessage> createState() => _ShowSoundMessageState();
}
class _ShowSoundMessageState extends State<ShowSoundMessage> {
bool isPlaying = false;
@override
void initState() {
super.initState();
SoundPlayer.playStateListener(listener: (_) {
if (_.playState == "complete") {
if (mounted) {
setState(() {
isPlaying = false;
});
}
}
});
SoundPlayer.setSoundInterruptListener(() {
if (mounted) {
setState(() {
isPlaying = false;
});
}
});
// eventBus.on().listen((event) {
// if (event == 'stop') {
// stopAndDispose();
// }
// });
}
void stopAndDispose() async {
if (isPlaying) {
await SoundPlayer.stop();
SoundPlayer.dispose();
}
}
_playSound() async {
if (!SoundPlayer.isInited) {
bool hasMicrophonePermission =
await Permission.microphone.request().isGranted;
bool hasStoragePermission =
Platform.isIOS || await Permission.storage.request().isGranted;
if (hasMicrophonePermission && hasStoragePermission) {
SoundPlayer.initSoundPlayer();
}
}
if (isPlaying) {
SoundPlayer.stop();
setState(() {
isPlaying = false;
});
} else {
SoundPlayer.play(url: widget.message.soundElem!.url!);
setState(() {
isPlaying = true;
});
}
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
_playSound();
},
child: Container(
constraints: BoxConstraints(
maxWidth: Get.width * 0.618,
minWidth: 64,
minHeight: 43,
),
width:
(widget.message.soundElem!.duration! / 60) * (Get.width * 0.618) +
64,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: widget.message.isSelf! ? AppColors.primary : AppColors.white,
),
child: Row(
mainAxisAlignment: widget.message.isSelf!
? MainAxisAlignment.start
: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${widget.message.soundElem!.duration}"',
style: TextStyle(
fontSize: AppSize.fontSize,
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
),
),
const SizedBox(width: 4),
Transform.rotate(
angle: widget.message.isSelf! ? pi : 0,
child: const Icon(
Icons.volume_up,
size: 18,
color: AppColors.white,
),
),
// Image.asset(
// widget.message.isSelf!
// ? isPlaying
// ? 'assets/chats/play_voice_send.gif'
// : 'assets/chats/voice_send.png'
// : isPlaying
// ? 'assets/chats/play_voice_receive.gif'
// : 'assets/chats/voice_receive.png',
// height: 16,
// color:
// widget.message.isSelf! ? AppColors.white : AppColors.primary,
// ),
],
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:chat/configs/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowTextMessage extends StatelessWidget {
final V2TimMessage message;
const ShowTextMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(
maxWidth: Get.width - 128,
minHeight: 43,
),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: message.isSelf! ? AppColors.primary : AppColors.white,
),
child: Text(
message.textElem!.text!,
overflow: TextOverflow.visible,
style: TextStyle(
fontSize: 14,
color: message.isSelf! ? AppColors.white : AppColors.active,
),
),
);
}
}

View File

@@ -0,0 +1,113 @@
import 'dart:convert';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/app_size.dart';
import 'package:chat/models/im/transfer_model.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin_platform_interface/models/v2_tim_message.dart';
class ShowTransferMessage extends StatefulWidget {
final V2TimMessage message;
const ShowTransferMessage(this.message, {Key? key}) : super(key: key);
@override
State<ShowTransferMessage> createState() => _ShowTransferMessageState();
}
class _ShowTransferMessageState extends State<ShowTransferMessage> {
@override
Widget build(BuildContext context) {
var model = TransferModel.fromJson(
json.decode(widget.message.customElem!.data!),
);
return InkWell(
onTap: () {
// Get.toNamed(
// ConversationRoutes.conversationTransferDetail,
// arguments: {
// 'order_id': model.orderId,
// },
// );
},
child: Container(
width: Get.width * 0.618,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: widget.message.isSelf! ? AppColors.primary : AppColors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.currency_exchange,
size: 44,
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
),
const SizedBox(
width: 8,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'',
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
),
),
Text(
model.amount,
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
fontSize: AppSize.titleFontSize,
fontWeight: FontWeight.bold,
),
),
],
),
Text(
'发起积分转账',
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
fontSize: AppSize.fontSize,
),
),
],
)
],
),
Divider(
height: 16,
color: widget.message.isSelf! ? AppColors.white : null,
),
Text(
'DT积分转账',
style: TextStyle(
color: widget.message.isSelf!
? AppColors.white
: AppColors.primary,
fontSize: AppSize.smallFontSize,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,68 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chat/configs/app_colors.dart';
import 'package:chat/views/conversation/preview/video_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
class ShowVideoMessage extends StatelessWidget {
final V2TimMessage message;
const ShowVideoMessage(this.message, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Get.to(
PreviewVideoWidget(message.videoElem!),
transition: Transition.zoom,
);
},
child: Container(
constraints: BoxConstraints(
maxWidth: Get.width * 0.382,
maxHeight: 192,
minHeight: 96,
minWidth: 96,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
children: [
_showVideSnapshot(),
const Positioned.fill(
child: Center(
child: Icon(
Icons.play_circle_outline,
size: 64,
color: AppColors.white,
),
),
),
],
),
),
),
);
}
Widget _showVideSnapshot() {
String ss = message.videoElem!.localSnapshotUrl!;
String? su = message.videoElem!.snapshotUrl;
if (ss.isNotEmpty && File(ss).existsSync()) {
return Image.file(
File(ss),
fit: BoxFit.cover,
);
} else if (su != null && su.isNotEmpty) {
return CachedNetworkImage(
imageUrl: su,
fit: BoxFit.cover,
);
} else {
return Container();
}
}
}

View File

@@ -0,0 +1,69 @@
import 'package:chat/configs/app_colors.dart';
import 'package:chat/configs/emoji.dart';
import 'package:chat/models/im/emoji_model.dart';
import 'package:flutter/material.dart';
class EmojiPanel extends StatelessWidget {
final void Function(int unicode) onTapEmoji;
const EmojiPanel({
Key? key,
required this.onTapEmoji,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
Container(
height: 248,
color: AppColors.page,
child: Stack(
children: [
GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 8,
childAspectRatio: 1,
),
children: emojiData.map(
(e) {
var item = EmojiModel.fromJson(e);
return InkWell(
onTap: () {
onTapEmoji(item.unicode);
},
child: Center(
child: EmojiItem(
name: item.name,
unicode: item.unicode,
),
),
);
},
).toList(),
),
],
),
),
],
),
);
}
}
class EmojiItem extends StatelessWidget {
const EmojiItem({Key? key, required this.name, required this.unicode})
: super(key: key);
final String name;
final int unicode;
@override
Widget build(BuildContext context) {
return Text(
String.fromCharCode(unicode),
style: const TextStyle(
fontSize: 26,
),
);
}
}