You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

924 lines
35 KiB

3 years ago
import 'dart:async';
import 'dart:convert';
// import 'dart:io';
import 'package:bytedesk_kefu/blocs/message_bloc/bloc.dart';
import 'package:bytedesk_kefu/blocs/thread_bloc/bloc.dart';
import 'package:bytedesk_kefu/model/messageProvider.dart';
import 'package:bytedesk_kefu/model/model.dart';
import 'package:bytedesk_kefu/mqtt/bytedesk_mqtt.dart';
import 'package:bytedesk_kefu/ui/chat/widget/message_widget.dart';
import 'package:bytedesk_kefu/ui/widget/expanded_viewport.dart';
import 'package:bytedesk_kefu/ui/widget/image_choose_widget.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:bytedesk_kefu/util/bytedesk_events.dart';
import 'package:bytedesk_kefu/util/bytedesk_utils.dart';
import 'package:bytedesk_kefu/util/bytedesk_uuid.dart';
// import 'package:bytedesk_kefu/util/bytedesk_utils.dart';
import 'package:file_picker/file_picker.dart';
import 'package:sp_util/sp_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:flutter_video_compress/flutter_video_compress.dart';
// import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
// import 'package:path_provider/path_provider.dart' as path_provider;
// TODO: 接通客服之前在title显示loading
// 客服关闭会话,或者 自动关闭会话,则禁止继续发送消息
// 点击商品信息,回调接口-进入商品详情页面
// TODO: 右上角增加按钮回调入口,支持用户自定义按钮,进入店铺/学校详情页面
// 系统消息居中显示
class ChatIMPage extends StatefulWidget {
//
final String? wid;
final String? aid;
final String? type;
final String? title;
final String? custom;
final String? postscript;
// 从历史会话或者点击通知栏进入
final bool? isThread;
final Thread? thread;
final ValueSetter<String>? customCallback;
//
ChatIMPage(
{Key? key,
this.wid,
this.aid,
this.type,
this.title,
this.custom,
this.postscript,
this.isThread,
this.thread,
this.customCallback})
: super(key: key);
//
@override
_ChatIMPageState createState() => _ChatIMPageState();
}
class _ChatIMPageState extends State<ChatIMPage>
with
AutomaticKeepAliveClientMixin<ChatIMPage>,
TickerProviderStateMixin,
WidgetsBindingObserver {
//
String? _title;
// 下拉刷新
RefreshController _refreshController = RefreshController();
// 输入文字
TextEditingController _textController = new TextEditingController();
// 滚动监听
ScrollController _scrollController = new ScrollController();
// 聊天记录本地存储
MessageProvider _messageProvider = new MessageProvider();
// 聊天记录内存存储
List<MessageWidget> _messages = <MessageWidget>[];
// 图片
ImagePicker _picker = ImagePicker();
// 长连接
BytedeskMqtt _bdMqtt = new BytedeskMqtt();
// 当前用户uid
String? _currentUid = SpUtil.getString(BytedeskConstants.uid);
String? _currentNickname = SpUtil.getString(BytedeskConstants.nickname);
String? _currentAvatar = SpUtil.getString(BytedeskConstants.avatar);
// 当前会话
Thread? _currentThread;
// 判断是否机器人对话状态
bool _isRobot = false;
// 分页加载聊天记录
int _page = 0;
int _size = 20;
// 延迟发送preview消息
Timer? _debounce;
// 定时拉取聊天记录
Timer? _loadHistoryTimer;
// 视频压缩
// final _flutterVideoCompress = FlutterVideoCompress();
//
@override
void initState() {
// print('chat_kf_page init');
SpUtil.putBool(BytedeskConstants.isCurrentChatKfPage, true);
// 从历史会话或者顶部通知栏进入
if (widget.isThread! && widget.thread != null) {
_currentThread = widget.thread;
_title = widget.thread!.nickname;
_getMessages(_page, _size);
} else {
// 从请求客服页面进入
_title = widget.title;
}
WidgetsBinding.instance!.addObserver(this);
// 监听build完成https://blog.csdn.net/baoolong/article/details/85097318
// WidgetsBinding.instance.addPostFrameCallback((_) {
// print('addPostFrameCallback');
// });
// Fluttertoast.showToast(msg: "请求中, 请稍后...");
_listener();
super.initState();
// 定时拉取聊天记录 10s
_loadHistoryTimer = Timer.periodic(Duration(seconds: 10), (timer) {
// print('从服务器 load history');
BlocProvider.of<MessageBloc>(context)
..add(LoadHistoryMessageEvent(uid: _currentUid, page: 0, size: 10));
// 每隔 1 秒钟会调用一次,如果要结束调用
// timer.cancel();
});
}
//
@override
Widget build(BuildContext context) {
super.build(context);
//
return Scaffold(
appBar: AppBar(
title: Text(_title ?? '请求中, 请稍后...'),
centerTitle: true,
elevation: 0,
actions: [
// TODO: 评价
// TODO: 常见问题
Visibility(
visible: _isRobot,
child: Align(
alignment: Alignment.centerRight,
child: Container(
padding: new EdgeInsets.only(right: 10),
width: 60,
child: InkWell(
onTap: () {
BlocProvider.of<ThreadBloc>(context)
..add(RequestAgentEvent(
wid: widget.wid,
aid: widget.aid,
type: widget.type));
},
child: Text(
'转人工',
style: TextStyle(color: Colors.black),
),
))),
)
],
),
body: MultiBlocListener(
listeners: [
BlocListener<ThreadBloc, ThreadState>(
listener: (context, state) {
// 隐藏toast
// Fluttertoast.cancel();
if (state is RequestThreadSuccess) {
setState(() {
_isRobot = false; // 需要,勿删
_currentThread = state.threadResult.msg!.thread!;
});
// 插入本地
// _messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
_getMessages(_page, _size);
// _appendMessage(state.threadResult.msg!);
//
if (state.threadResult.statusCode == 200 ||
state.threadResult.statusCode == 201) {
print('创建新会话');
// 插入本地
// _messageProvider.insert(state.threadResult.msg!);
// TODO: 参考拼多多在发送按钮上方显示pop商品信息用户确认之后才会发送商品信息
// 发送商品信息
if (widget.custom != null &&
widget.custom!.trim().length > 0) {
_bdMqtt.sendCommodityMessage(
widget.custom!, _currentThread!);
}
// 发送附言消息
if (widget.postscript != null &&
widget.postscript!.trim().length > 0) {
_bdMqtt.sendTextMessage(
widget.postscript!, _currentThread!);
}
} else if (state.threadResult.statusCode == 202) {
print('提示排队中');
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
// _getMessages(_page, _size);
_appendMessage(state.threadResult.msg!);
// 发送商品信息
if (widget.custom != null &&
widget.custom!.trim().length > 0) {
_bdMqtt.sendCommodityMessage(
widget.custom!, _currentThread!);
}
// 发送附言消息
if (widget.postscript != null &&
widget.postscript!.trim().length > 0) {
_bdMqtt.sendTextMessage(
widget.postscript!, _currentThread!);
}
} else if (state.threadResult.statusCode == 203) {
print('当前非工作时间,请自助查询或留言');
// TODO: 显示留言页面
setState(() {
_currentThread = state.threadResult.msg!.thread;
});
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
_getMessages(_page, _size);
_appendMessage(state.threadResult.msg!);
} else if (state.threadResult.statusCode == 204) {
print('当前无客服在线,请自助查询或留言');
// TODO: 显示留言页面
setState(() {
_currentThread = state.threadResult.msg!.thread;
});
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
_getMessages(_page, _size);
_appendMessage(state.threadResult.msg!);
} else if (state.threadResult.statusCode == 205) {
print('咨询前问卷');
setState(() {
_currentThread = state.threadResult.msg!.thread;
});
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
_getMessages(_page, _size);
_appendMessage(state.threadResult.msg!);
//
} else if (state.threadResult.statusCode == 206) {
print('返回机器人初始欢迎语 + 欢迎问题列表');
// TODO: 显示问题列表
setState(() {
_isRobot = true;
_currentThread = state.threadResult.msg!.thread;
});
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// 加载本地历史消息
_getMessages(_page, _size);
_appendMessage(state.threadResult.msg!);
//
} else if (state.threadResult.statusCode == -1) {
Fluttertoast.showToast(msg: "请求会话失败");
} else if (state.threadResult.statusCode == -2) {
Fluttertoast.showToast(msg: "siteId或者工作组id错误");
} else if (state.threadResult.statusCode == -3) {
Fluttertoast.showToast(msg: "您已经被禁言");
} else {
Fluttertoast.showToast(msg: "请求会话失败");
}
} else if (state is RequestAgentSuccess) {
// 请求人工客服,不管此工作组是否设置为默认机器人,只要有人工客服在线,则可以直接对接人工
setState(() {
_isRobot = false; // 需要,勿删
_currentThread = state.threadResult.msg!.thread;
});
// 插入本地
_messageProvider.insert(state.threadResult.msg!);
// _appendMessage(state.threadResult.msg!);
}
},
),
BlocListener<MessageBloc, MessageState>(
listener: (context, state) {
// print('message state change');
if (state is ReceiveMessageState) {
print('receive message:' + state.message!.content!);
} else if (state is UploadImageSuccess) {
_bdMqtt.sendImageMessage(
state.uploadJsonResult.url!, _currentThread!);
} else if (state is UploadVideoSuccess) {
_bdMqtt.sendVideoMessage(
state.uploadJsonResult.url!, _currentThread!);
} else if (state is QueryAnswerSuccess) {
Message queryMessage = state.query!;
queryMessage.isSend = 1;
_messageProvider.insert(queryMessage);
_appendMessage(queryMessage);
//
_messageProvider.insert(state.answer!);
_appendMessage(state.answer!);
} else if (state is MessageAnswerSuccess) {
Message queryMessage = state.query!;
queryMessage.isSend = 1;
_messageProvider.insert(queryMessage);
_appendMessage(queryMessage);
//
if (state.query!.content!.contains('人工')) {
BlocProvider.of<ThreadBloc>(context)
..add(RequestAgentEvent(
wid: widget.wid,
aid: widget.aid,
type: widget.type));
} else {
_messageProvider.insert(state.answer!);
_appendMessage(state.answer!);
}
} else if (state is RateAnswerSuccess) {
// TODO:
} else if (state is LoadHistoryMessageSuccess) {
// TODO: 插入历史聊天记录
// print('history ${state.messageList!.length}');
for (var i = 0; i < state.messageList!.length; i++) {
// Message message = state.messageList[i];
// _appendMessage(message);
}
} else if (state is LoadTopicMessageSuccess) {
// TODO: 插入历史聊天记录
print('topic history ${state.messageList!.length}');
for (var i = 0; i < state.messageList!.length; i++) {
// Message message = state.messageList[i];
// _appendMessage(message);
}
}
},
),
],
child: Container(
alignment: Alignment.bottomCenter,
color: Color(0xFFDEEEEEE),
child: Column(
children: <Widget>[
// Expanded(
// child: RefreshIndicator(
// child: ListView.builder(
// padding: EdgeInsets.all(8.0),
// // reverse: true,
// controller: _scrollController,
// itemCount: _messages.length,
// itemBuilder: (_, int index) => _messages[index],
// ),
// onRefresh: _loadMoreMessages,
// ),
// ),
// Expanded(
// child: ListView.builder(
// padding: new EdgeInsets.all(8.0),
// reverse: true,
// shrinkWrap: true,
// controller: _scrollController,
// itemBuilder: (_, int index) => _messages[index],
// itemCount: _messages.length,
// ),
// ),
// 参考pull_to_refresh库中 QQChatList例子
Expanded(
child: SmartRefresher(
enablePullDown: false,
onLoading: () async {
print('TODO: 下拉刷新'); // 注意:方向跟默认是反着的
// await Future.delayed(Duration(milliseconds: 1000));
_getMessages(_page, _size);
setState(() {});
_refreshController.loadComplete();
},
footer: ClassicFooter(
loadStyle: LoadStyle.ShowWhenLoading,
),
enablePullUp: true,
child: Scrollable(
controller: _scrollController,
axisDirection: AxisDirection.up,
viewportBuilder: (context, offset) {
return ExpandedViewport(
offset: offset,
axisDirection: AxisDirection.up,
slivers: <Widget>[
SliverExpanded(),
SliverList(
delegate: SliverChildBuilderDelegate(
(c, i) => _messages[i],
childCount: _messages.length),
)
],
);
},
),
controller: _refreshController,
),
),
Divider(
height: 1.0,
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
),
child: _textComposerWidget(),
),
],
),
)));
}
//
Widget _textComposerWidget() {
return IconTheme(
data: IconThemeData(color: Colors.blue),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 54,
child: Row(
children: <Widget>[
Container(
// margin: const EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.add),
onPressed: () {
// _getImage();
showModalBottomSheet(
context: context,
builder: (context) {
return ImageChooseWidget(pickImageCallBack: () {
_pickImage();
}, takeImageCallBack: () {
_takeImage();
}, pickVideoCallBack: () {
_pickVideo();
}, captureVideoCallBack: () {
_captureVideo();
});
});
},
),
),
Flexible(
child: TextField(
onChanged: (value) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
// 积累500毫秒再发送。否则发送过于频繁
_debounce = Timer(const Duration(milliseconds: 500), () {
print('send preview $value');
// 发送预知消息 value != null &&
if (value.trim().length > 0) {
_bdMqtt.sendPreviewMessage(value, _currentThread!);
} else {
_bdMqtt.sendPreviewMessage('', _currentThread!);
}
});
},
decoration: InputDecoration.collapsed(hintText: "输入内容..."),
controller: _textController,
onSubmitted: _handleSubmitted,
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
icon: Icon(Icons.send),
onPressed: () => _handleSubmitted(_textController.text),
),
)
],
),
),
);
}
//
@override
bool get wantKeepAlive => true;
//
void _handleSubmitted(String? text) {
_textController.clear();
//
if (text!.trim().length == 0) {
return;
}
//
if (_isRobot) {
// 请求机器人答案
BlocProvider.of<MessageBloc>(context)
..add(MessageAnswerEvent(
type: widget.type,
wid: widget.wid,
aid: widget.aid,
content: text));
} else if (_bdMqtt.isConnected()) {
if (_currentThread == null) {
Fluttertoast.showToast(msg: '请求客服中, 请稍后...');
return;
}
// 长连接正常情况下,调用长连接接口
_bdMqtt.sendTextMessage(text, _currentThread!);
} else {
print('长连接断开的情况下调用rest接口');
String? mid = BytedeskUuid.generateV4();
String? timestamp = BytedeskUtils.formatedDateNow();
String? client = BytedeskUtils.getClient();
String? type = BytedeskConstants.MESSAGE_TYPE_TEXT;
//
var jsonContent = {
"mid": mid,
"timestamp": timestamp,
"client": client,
"version": "1",
"type": type,
"user": {
"uid": this._currentUid,
"nickname": this._currentNickname,
"avatar": this._currentAvatar
},
"text": {"content": text},
"thread": {
"tid": this._currentThread!.tid,
"type": this._currentThread!.type,
"content": text,
"nickname": this._currentThread!.nickname,
"avatar": this._currentThread!.avatar,
"topic": this._currentThread!.topic,
"timestamp": timestamp,
"unreadCount": 0
}
};
String? jsonString = json.encode(jsonContent);
BlocProvider.of<MessageBloc>(context)
..add(SendMessageRestEvent(json: jsonString));
// 暂时没有将插入本地函数独立出来,暂时
Message message = new Message();
message.mid = mid;
message.type = type;
message.timestamp = timestamp;
// message.client = client;
message.avatar = _currentAvatar;
message.topic = this._currentThread!.topic;
message.status = BytedeskConstants.MESSAGE_STATUS_SENDING;
message.isSend = 1;
message.currentUid = this._currentUid;
message.answersJson = '';
message.thread = this._currentThread;
message.user = User(
uid: this._currentUid,
avatar: this._currentAvatar,
nickname: this._currentNickname);
//
message.content = text;
// 插入本地数据库
// if (_messageProvider != null) {
_messageProvider.insert(message);
// }
}
}
//
_listener() {
// 更新消息状态
bytedeskEventBus.on<ReceiveMessageReceiptEventBus>().listen((event) {
// print('更新状态:' + event.status);
if (!this.mounted) {
return;
}
// 更新界面, FIXME: 只有插入新消息,才会更新?
for (var i = 0; i < _messages.length; i++) {
MessageWidget messageWidget = _messages[i];
if (messageWidget.message!.mid == event.mid &&
_messages[i].message!.status !=
BytedeskConstants.MESSAGE_STATUS_READ) {
setState(() {
_messages[i].message!.status = event.status;
});
}
}
});
bytedeskEventBus.on<ReceiveMessagePreviewEventBus>().listen((event) {
// print('消息预知');
if (this.mounted) {
setState(() {
// TODO: 国际化,支持英文
_title = '对方正在输入';
});
}
// 还原title
Timer(
Duration(seconds: 3),
() {
// print('timer');
if (this.mounted) {
setState(() {
_title = widget.title;
});
}
},
);
});
// 同 DeleteMessageEventBus 事件
// bytedeskEventBus.on<ReceiveMessageRecallEventBus>().listen((event) {
// print('消息撤回');
// });
// 接收到新消息
bytedeskEventBus.on<ReceiveMessageEventBus>().listen((event) {
// print('receive message:' + event.message!.content);
if (_currentThread != null &&
(event.message.thread!.topic != _currentThread!.topic)) {
return;
}
// 非自己发送的,发送已读回执
if (event.message.isSend == 0) {
_bdMqtt.sendReceiptReadMessage(
event.message.mid!, event.message.thread!);
}
// 界面显示
MessageWidget messageWidget = new MessageWidget(
message: event.message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
if (this.mounted) {
setState(() {
_messages.insert(0, messageWidget);
// _messages.add(messageWidget);
});
}
});
// 删除消息
bytedeskEventBus.on<DeleteMessageEventBus>().listen((event) {
//
if (this.mounted) {
// 从sqlite中删除
_messageProvider.delete(event.mid);
// 更新界面
setState(() {
_messages.removeWhere((element) => element.message!.mid == event.mid);
});
}
});
// 查询机器人消息
bytedeskEventBus.on<QueryAnswerEventBus>().listen((event) {
//
if (this.mounted) {
print(
'aid ${event.aid}, question ${event.question}, answer ${event.answer}');
// 可以直接将问题和答案插入本地,并显示,但为了服务器保存查询记录,特将请求发送给服务器
BlocProvider.of<MessageBloc>(context)
..add(QueryAnswerEvent(
tid: _currentThread!.tid,
aid: event.aid,
));
}
});
// 滚动监听, https://learnku.com/articles/30338
_scrollController.addListener(() {
// 隐藏软键盘
FocusScope.of(context).requestFocus(FocusNode());
// 如果滑动到底部
// if (_scrollController.position.pixels ==
// _scrollController.position.maxScrollExtent) {
// print('已经到底了');
// }
});
}
// 选择图片
Future<void> _pickImage() async {
try {
XFile? pickedFile = await _picker.pickImage(
source: ImageSource.gallery, maxWidth: 800, imageQuality: 95);
//
if (pickedFile != null) {
print('pick image path: ${pickedFile.path}');
// TODO: 将图片显示到对话消息中
// TODO: 显示处理中loading
// 压缩
// final dir = await path_provider.getTemporaryDirectory();
// final targetPath = dir.absolute.path +
// "/" +
// BytedeskUtils.currentTimeMillis().toString() +
// ".jpg";
// print('targetPath: $targetPath');
// await BytedeskUtils.compressImage(File(pickedFile.path), targetPath);
// // 上传压缩后图片
// BlocProvider.of<MessageBloc>(context)
// ..add(UploadImageEvent(filePath: targetPath));
//
BlocProvider.of<MessageBloc>(context)
..add(UploadImageEvent(filePath: pickedFile.path));
} else {
Fluttertoast.showToast(msg: '未选取图片');
}
} catch (e) {
print('pick image error ${e.toString()}');
Fluttertoast.showToast(msg: "未选取图片");
}
}
// 拍照
Future<void> _takeImage() async {
try {
XFile? pickedFile = await _picker.pickImage(
source: ImageSource.camera, maxWidth: 800, imageQuality: 95);
//
if (pickedFile != null) {
print('take image path: ${pickedFile.path}');
// TODO: 将图片显示到对话消息中
// TODO: 显示处理中loading
// 压缩
// final dir = await path_provider.getTemporaryDirectory();
// final targetPath = dir.absolute.path +
// "/" +
// BytedeskUtils.currentTimeMillis().toString() +
// ".jpg";
// print('targetPath: $targetPath');
// await BytedeskUtils.compressImage(File(pickedFile.path), targetPath);
// // 上传压缩后图片
// BlocProvider.of<MessageBloc>(context)
// ..add(UploadImageEvent(filePath: targetPath));
//
BlocProvider.of<MessageBloc>(context)
..add(UploadImageEvent(filePath: pickedFile.path));
} else {
Fluttertoast.showToast(msg: '未拍照');
}
} catch (e) {
print('take image error ${e.toString()}');
Fluttertoast.showToast(msg: "未选取图片");
}
}
// 上传视频
// FIXME: image_picker有bug选择视频后缀为.jpg
// 手机可以播放但chrome无法播放
Future<void> _pickVideo() async {
try {
// final PickedFile videoFile = await _picker.getVideo(
// source: ImageSource.gallery, maxDuration: const Duration(seconds: 10));
// print('pick video path: ${videoFile.path}');
// if (videoFile != null) {
// BlocProvider.of<MessageBloc>(context)
// ..add(UploadVideoEvent(filePath: videoFile.path));
// } else {
// Fluttertoast.showToast(msg: '未选取视频');
// }
// 使用file_picker替换image_picker
List<PlatformFile>? _paths = (await FilePicker.platform.pickFiles(
type: FileType.video,
allowMultiple: false,
allowedExtensions: [],
))
?.files;
if (_paths!.length > 0) {
// TODO: 将视频显示到对话消息中
// TODO: 显示处理中loading
// 压缩
// final info = await _flutterVideoCompress.compressVideo(
// _paths[0].path,
// quality:
// VideoQuality.LowQuality, // default(VideoQuality.DefaultQuality)
// deleteOrigin: false, // default(false)
// );
// // debugPrint(info.toJson().toString());
// String? afterPath = info.toJson()['path'];
// // print('video path: ${_paths[0].path}, compress path: $afterPath');
// // 上传
// BlocProvider.of<MessageBloc>(context)
// ..add(UploadVideoEvent(filePath: afterPath));
// 压缩后上传
BlocProvider.of<MessageBloc>(context)
..add(UploadVideoEvent(filePath: _paths[0].path));
}
} catch (e) {
print('pick video error ${e.toString()}');
Fluttertoast.showToast(msg: "未选取视频");
}
}
// 录制视频
Future<void> _captureVideo() async {
try {
XFile? pickedFile = await _picker.pickVideo(
source: ImageSource.camera, maxDuration: const Duration(seconds: 10));
//
if (pickedFile != null) {
print('take video path: ${pickedFile.path}');
// TODO: 将图片显示到对话消息中
// TODO: 显示处理中loading
// 压缩
// final info = await _flutterVideoCompress.compressVideo(
// pickedFile.path,
// quality:
// VideoQuality.LowQuality, // default(VideoQuality.DefaultQuality)
// deleteOrigin: false, // default(false)
// );
// // debugPrint(info.toJson().toString());
// String? afterPath = info.toJson()['path'];
// // print('video path: ${pickedFile.path}, compress path: $afterPath');
// // 上传
// BlocProvider.of<MessageBloc>(context)
// ..add(UploadVideoEvent(filePath: afterPath));
//
BlocProvider.of<MessageBloc>(context)
..add(UploadVideoEvent(filePath: pickedFile.path));
} else {
Fluttertoast.showToast(msg: '未录制视频');
}
} catch (e) {
print('take video error ${e.toString()}');
Fluttertoast.showToast(msg: "未录制视频");
}
}
// 加载更多聊天记录
// Future<void> _loadMoreMessages() async {
// print('load more');
// // TODO: 从服务器加载
// _getMessages(_page, _size);
// }
// 分页加载本地历史聊天记录
// TODO: 从服务器加载聊天记录
Future<Null> _getMessages(int page, int size) async {
//
List<Message> messageList = await _messageProvider.getTopicMessages(
_currentThread!.topic, _currentUid, page, size);
messageList.forEach((message) {
MessageWidget messageWidget = new MessageWidget(
message: message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
if (this.mounted) {
setState(() {
// _messages.insert(0, messageWidget);
_messages.add(messageWidget);
});
}
});
//
_page += 1;
}
Future<Null> _appendMessage(Message message) async {
// print('append:' + message!.mid);
bool contains = false;
for (var i = 0; i < _messages.length; i++) {
Message? element = _messages[i].message;
if (element?.mid == message.mid) {
contains = true;
}
}
if (!contains) {
_messageProvider.insert(message);
MessageWidget messageWidget = new MessageWidget(
message: message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
if (this.mounted) {
setState(() {
_messages.insert(0, messageWidget);
});
}
}
}
void scrollToBottom() {
// After 1 second, it takes you to the bottom of the ListView
// Timer(
// Duration(seconds: 1),
// () => _scrollController.jumpTo(_scrollController.position.maxScrollExtent),
// );
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// print("didChangeAppLifecycleState:" + state.toString());
switch (state) {
case AppLifecycleState.inactive: // 处于这种状态的应用程序应该假设它们可能在任何时候暂停。
break;
case AppLifecycleState.paused: // 应用程序不可见,后台
break;
case AppLifecycleState.resumed: // 应用程序可见,前台
// APP切换到前台之后重连
// BytedeskUtils.mqttReConnect();
// TODO: 拉取离线消息
break;
case AppLifecycleState.detached: // 申请将暂时暂停
break;
}
}
@override
void dispose() {
// print('chat_kf_page dispose');
SpUtil.putBool(BytedeskConstants.isCurrentChatKfPage, false);
WidgetsBinding.instance!.removeObserver(this);
_debounce?.cancel();
_loadHistoryTimer?.cancel();
super.dispose();
}
}