import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flustars/flustars.dart' as flustars; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:liftmanager/common/common.dart'; import 'package:liftmanager/internal/bbs/page/jubao_page.dart'; import 'package:liftmanager/net/api_service.dart'; import 'package:liftmanager/res/iconfont.dart'; import 'package:liftmanager/utils/fast_notification.dart'; import 'package:liftmanager/utils/oss_upload.dart'; import 'package:liftmanager/utils/toast.dart'; import 'package:liftmanager/widgets/app_bar.dart'; import 'package:liftmanager/widgets/chat_list_view.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; // import 'package:shared_preferences/shared_preferences.dart'; import './chat_detail/chat_content_view.dart'; import '../model/conversation.dart'; import '../provide/websocket.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:flutter_sound/public/flutter_sound_player.dart'; import 'package:flutter_sound/public/flutter_sound_recorder.dart'; class ChatDetailPage extends StatefulWidget { ChatDetailPage( {this.id, this.type, this.toUserId, this.jubaoToUserId, this.title = '聊天室'}); final String id; final String type; final String toUserId; final String title; String jubaoToUserId; @override _ChatDetailPageState createState() => _ChatDetailPageState(); // _ChatDetailPageState createState() => _ChatDetailPageState(type,index); } class _ChatDetailPageState extends State { bool hasText = false; bool isAutoJump = true; bool isDownKeyboard = true; int type = 0; int index = 1; var msgList = []; var msgListBack = []; var assList = []; bool showEmoji = false; bool hasMore = true; int _page = 1; int pageSize = 10; Conversation data; // _ChatDetailPageState(this.type0,this.index0); WebSocketProvide provider = WebSocketProvide(); ScrollController _scrollController; var userList; var storyList = []; var storyListUserOnline = []; //避免从附近的人打招呼重复的initRoom bool isTrim = false; int dataTable; final controller = TextEditingController(); bool voiceModeOn = false; bool voiceRecordingOn = false; FlutterSoundPlayer _player = FlutterSoundPlayer(); FlutterSoundRecorder _recorder = FlutterSoundRecorder(); bool _playerIsInited = false; bool _recorderIsInited = false; bool _playbackReady = true; String _audioFilePath; int recordingDuration = 0; Timer recordingTimer; AnimationController _animationController; void _handleSubmitted(context, String text) { // FocusScope.of(context).requestFocus(FocusNode()); if (controller.text.length > 0) { // FocusScope.of(context).requestFocus(FocusNode()); print('发送${text}'); print(type); if (Provider.of(context, listen: false) .socketIsConnect) { controller.clear(); //清空输入框 } Provider.of(context, listen: false).sendMessage(context, msg: text, id: widget.id, msgType: 1, dataTable: dataTable, toUserId: widget.toUserId); showEmoji = false; hasMore = true; _page = 1; // FocusScope.of(context).requestFocus(FocusNode()); } else { toasts("请输入"); } } void _jumpBottom() { _scrollController.animateTo(_scrollController.position.maxScrollExtent, curve: Curves.easeOut, duration: Duration(milliseconds: 200)); } String emoji = "😀,😁,😂,😃,😄,😅,😆,😉,😊,😋,😎,😍,😘,😗,😙,😚,😇,😐,😑,😶,😏,😣,😥,😮,😯,😪,😫,😴,😌,😛,😜,😝,😒,😓,😔,😕,😲,😷,😖,😞,😟,😤,😢,😭,😦,😧,😨,😬,😰,😱,😳,😵,😡,😠"; var emojiList = []; void setWebSocket() async { // SharedPreferences prefs = await SharedPreferences.getInstance(); new Future.delayed(Duration(milliseconds: 500), () { Provider.of(context, listen: false) .incomeRoom(widget.id); // getStringEvent(widget.id); }); } void initJump() async { new Future.delayed(Duration(milliseconds: 500), () { FocusScope.of(context).requestFocus(FocusNode()); _jumpBottom(); }); } void disposeJump() async { new Future.delayed(Duration(milliseconds: 500), () { Provider.of(context, listen: false).closeWebSocket(); }); } // void getMsgList() async { // new Future.delayed(Duration(milliseconds: 2000), () { // setState(() { // msgList = Provider.of(context, listen: false) // .historyMessageqqq; // print('getMsgList msgList ===== $msgList'); // _jumpBottom(); // }); // }); // } // 输入框的焦点实例 FocusNode _focusNode; @override void initState() { _player.openAudioSession().then((value) { setState(() { _playerIsInited = true; }); }); openTheRecorder().then((value) { setState(() { _recorderIsInited = true; }); }); emojiList = emoji.split(','); if (widget.type == "nearToOne") { dataTable = 3; } else { dataTable = null; var roomIds = flustars.SpUtil.getStringList('showAlert'); if (!roomIds.contains(widget.id)) { Fluttertoast.showToast( msg: '欢迎使用,请在使用过程中注意个人信息及财产安全', backgroundColor: Colors.amber, textColor: Colors.black, fontSize: 13, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.TOP, timeInSecForIosWeb: 3); } else { var newRoodIds = [widget.id]; newRoodIds.addAll(roomIds); flustars.SpUtil.putStringList('showAlert', newRoodIds); } } print(widget.id); FastNotification.addListener("chat_room", (historyMessageqqq) { if (mounted) { setState(() { msgList = historyMessageqqq; assList = historyMessageqqq; print('chat_room msgList ===== $msgList'); }); _jumpBottom(); } }); FastNotification.addListener("chat_room_chat", (historyMessageqqq) { if (mounted) { setState(() { msgList = historyMessageqqq; assList = historyMessageqqq; print('chat_room_chat msgList ===== $msgList'); }); _jumpBottom(); } }); FastNotification.addListener("set_user", (setUserList) { if (mounted) { new Future.delayed(Duration(milliseconds: 1000), () { // setState(() { // userList = setUserList; // }); }); } }); FastNotification.addListener("initSocket", (initThisSocket) { if (mounted) { isTrim = true; setWebSocket(); _jumpBottom(); FocusScope.of(context).requestFocus(FocusNode()); toasts("聊天室已重连!"); } }); if (!Provider.of(context, listen: false) .socketIsConnect) { Provider.of(context, listen: false).createWebsocket( Provider.of(context, listen: false).roomId); } super.initState(); _scrollController = new ScrollController(); _scrollController.addListener(() { // FocusScope.of(context).requestFocus(FocusNode()); // print(_controller.offset); }); setWebSocket(); _focusNode = FocusNode(); _focusNode.addListener(() { // _jumpBottom(); Timer(Duration(milliseconds: 300), () => _jumpBottom()); if (!_focusNode.hasFocus && !isDownKeyboard) { FocusScope.of(context).requestFocus(_focusNode); } if (!_focusNode.hasFocus) { setState(() { showEmoji = false; }); } isDownKeyboard = true; }); } @override void dispose() { stopPlayer(); _player.closeAudioSession(); _player = null; stopRecorder(); _recorder.closeAudioSession(); _recorder = null; if (_audioFilePath != null) { var outputFile = File(_audioFilePath); if (outputFile.existsSync()) { outputFile.delete(); } } super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); } void getStringEvent(roomId) async { // SharedPreferences prefs = await SharedPreferences.getInstance(); var storyList = []; var storyListUserOnline = []; // if (prefs.getStringList(roomId) != null) { // List hisString = prefs.getStringList(roomId); // storyList = hisString.map((item) => jsonDecode(item)).toList(); // } else { // prefs.setStringList(roomId, []); // } // if (flustars.SpUtil.getString(roomId + "userOnline") != null && // flustars.SpUtil.getString(roomId + "userOnline") != "") { // // if(prefs.getString(roomId+"userOnline")!=null){ // // String hisStringUserOnline = prefs.getString(roomId+"userOnline"); // // flustars.SpUtil.putString(roomId+"userOnline", res.token); // String hisStringUserOnline = // flustars.SpUtil.getString(roomId + "userOnline"); // for (var value in JsonDecoder().convert(hisStringUserOnline)) { // print(value); // storyListUserOnline.add(value); // } // ; // // storyListUserOnline = JsonDecoder().convert(hisStringUserOnline); // // storyListUserOnline = hisStringUserOnline.map((item)=>jsonDecode(item)).toList(); // } else { // // prefs.setString(roomId+"userOnline",''); // } // print(JsonEncoder().convert(storyList)); // print(JsonEncoder().convert(storyListUserOnline)); new Future.delayed(Duration(milliseconds: 1000), () { setState(() { if (storyList.length > 0) { msgList = storyList; print('storyList msgList ===== $msgList'); } if (storyListUserOnline.length > 0) { userList = storyListUserOnline; } print(JsonEncoder().convert(msgList)); _jumpBottom(); }); }); } // @override // void didChangeDependencies() { // // Provider.of(context).closeWebSocket(); // Provider.of(context,listen: false).closeWebSocket(); // super.didChangeDependencies(); // } FocusNode blankNode = FocusNode(); ///选择图片 void selectPicker() { showDialog( context: context, builder: (BuildContext context) { return SimpleDialog( title: Text("选择方式"), children: ["拍照", '从手机相册选择'].map((String value) { print("$value"); return SimpleDialogOption( child: Text( "${value}", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), onPressed: () { _getImage(value == '拍照' ? 1 : 0); Navigator.of(context).pop(); }, ); }).toList()); }); } var imagesList = []; var imagesListLast = []; void _getImage(int key) async { try { var _imageFile = await ImagePicker.pickImage( source: key == 1 ? ImageSource.camera : ImageSource.gallery, maxWidth: 800, imageQuality: 95); print(_imageFile); print(3333); if (_imageFile != null) { // images.add(_imageFile); upLoadFileOnce(_imageFile.path); // setState(() {}); } } catch (e) { toasts("没有权限,无法打开相册!"); } } upLoadFileOnce(path) { showLoading(context, "正在发送..."); NewApiService().upload(path, onSuccess: (res) { // imagesUrl.add(res.path); dismissLoading(context); String imagesUrl; imagesUrl = (res.pathUrl); Provider.of(context, listen: false).sendMessage(context, msg: imagesUrl, id: widget.id, msgType: 2, dataTable: dataTable, toUserId: widget.toUserId); }, onError: (code, msg) { dismissLoading(context); toasts(msg); }); } @override Widget build(BuildContext context) { msgList = processMsgList(msgList); imagesList = []; msgListBack = []; msgList.forEach((element) { msgListBack.insert(0, element); }); // print(JsonEncoder().convert(msgListBack)); print('msgList ======= $msgList'); print('msgList count ======= ${msgList.length}'); try { if (msgList.length > 0) { msgList.forEach((element) { if (element["type"] == 2) { imagesList.add(element["msg"]); } }); } } catch (e) { print('msgList e ======= $e'); } if (isAutoJump) { Timer(Duration(milliseconds: 500), () { _jumpBottom(); }); } else { isAutoJump = true; } return // ChangeNotifierProvider( // create: (_) => provider, // child: Scaffold( appBar: MyAppBar( centerTitle: widget.title, actions: [ new FlatButton( onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => JuBaoPage( toUserId: widget.jubaoToUserId, ))); }, child: Text("举报"), ), ], // onPressed:(){ // NavigatorUtils.push(context, FriendsRouter.friendsList); // } isFun: true, fun: () { Navigator.popUntil(context, ModalRoute.withName('/home')); }, ), body: SafeArea( child: Stack( alignment: Alignment.center, children: [ GestureDetector( onTap: () { // 点击空白页面关闭键盘 // FocusScope.of(context).requestFocus(_focusNode); FocusScope.of(context).requestFocus(FocusNode()); setState(() { showEmoji = false; }); }, child: Container( padding: EdgeInsets.only(top: 10), color: Color(0xffF6F6F6), child: Column( children: [ Expanded( child: ChatListView( key: Key('chat_list'), scrollController: _scrollController, onRefresh: _loadMore, pageSize: 10, itemCount: msgList.length, hasMore: hasMore, itemBuilder: (_, index) { int typeUser; try { if ('${msgList[index]["fromUser"]}' == flustars.SpUtil.getString(Constant.userId)) { typeUser = 1; } else { typeUser = 0; } return ChatContentView( contentIndex: index, urlList: imagesList, type: typeUser, text: msgList[index]["msg"], msgType: msgList[index]["type"], avatar: msgList[index]["avatarUrl"], username: msgList[index]["name"], remarks: msgList[index]["remarks"], isNetwork: msgList[index]["avatarUrl"] != null ? true : false, recordingDuration: msgList[index]["dura"], onTap: () async { if (_player.isPlaying) { await stopPlayer(); } print('play ===== ${msgList[index]['msg']}'); play(msgList[index]['msg'], () { print('stopped ===== '); }); }); } catch (e) { print('e ==== $e'); } return null; }, ), ), showEmoji ? Container( padding: EdgeInsets.only( left: ScreenUtil().setWidth(6), top: ScreenUtil().setWidth(10), bottom: ScreenUtil().setWidth(10), right: ScreenUtil().setWidth(6)), color: Colors.white, height: ScreenUtil().setWidth(200), child: GridView.extent( //横轴的最大长度 maxCrossAxisExtent: 35, //内边距 padding: EdgeInsets.all(4.0), //垂直方向的间距 mainAxisSpacing: 4.0, //水平方向的间距 crossAxisSpacing: 4.0, children: emojiList.map((element) { return GestureDetector( onTap: () { controller.text += element; }, child: Text( element, style: TextStyle( color: Color(0xff000000), fontSize: ScreenUtil().setSp(22)), textAlign: TextAlign.center, ), ); }).toList(), )) : Container( child: null, ), Container( padding: EdgeInsets.only( top: ScreenUtil().setHeight(2.0), bottom: ScreenUtil().setHeight(2.0), left: 0, right: 0), color: Color(0xffFBFBFB), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( icon: voiceModeOn ? Icon( Iconfont.b040jianpan, size: 21, ) : Icon( Iconfont.yuyin2, size: 21, ), onPressed: () { setState(() { voiceModeOn = !voiceModeOn; }); }), if (voiceModeOn) Expanded( child: GestureDetector( onLongPressStart: (details) { setState(() { voiceRecordingOn = true; }); if (_player.isPlaying) { stopPlayer(); } record(); startCountingRecording(); }, onLongPressEnd: (details) async { setState(() { voiceRecordingOn = false; }); // print('localPosition ===== ${details.localPosition}'); if (recordingTimer != null) { recordingTimer.cancel(); } await stopRecorder(); if (details.localPosition.dy > -50) { await uploadAudioFile(_audioFilePath); } else { toasts("已取消"); } }, child: Text( voiceRecordingOn ? '松开结束' : '按住说话', textAlign: TextAlign.center, ), ), ), if (!voiceModeOn) Expanded( child: Container( height: 40, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5.0)), color: Colors.white), child: TextFormField( controller: controller, // decoration: InputDecoration.collapsed(hintText: null), decoration: InputDecoration( border: InputBorder.none, ), maxLines: 5, minLines: 1, autocorrect: true, autofocus: true, focusNode: _focusNode, textAlign: TextAlign.start, textInputAction: TextInputAction.send, style: TextStyle(color: Colors.black), cursorColor: Colors.green, onFieldSubmitted: (term) { isDownKeyboard = false; // 这里进行事件处理 // print('点击点击点击'); if (controller.text == null || controller.text == '') { toasts("请输入内容"); } else { _handleSubmitted(context, controller.text); } }, onChanged: (text) { setState(() { hasText = text.length > 0 ? true : false; }); // print('change=================== $text'); }, // onSubmitted:_handleSubmitted, enabled: true, //bu禁用 ), )), Container( width: ScreenUtil().setWidth(30.0), child: IconButton( icon: Icon( Iconfont.biaoqing, size: 21, ), //发送按钮图标 onPressed: () { print('打开表情面板'); setState(() { showEmoji = !showEmoji; voiceModeOn = false; }); }), ), Container( width: ScreenUtil().setWidth(30.0), margin: EdgeInsets.only( right: ScreenUtil().setWidth(10.0)), child: IconButton( icon: Icon(Iconfont.tianjia, size: 24), onPressed: () { selectPicker(); FocusScope.of(context) .requestFocus(FocusNode()); }), ), Container( width: ScreenUtil().setWidth(30.0), margin: EdgeInsets.only( right: ScreenUtil().setWidth(10.0)), child: IconButton( //发送按钮或者+按钮 icon: Icon(Iconfont.fasong, size: 18, color: Color(0xff3978F7)), onPressed: () { // isDownKeyboard = false; if (controller.text == null || controller.text == '') { toasts("请输入内容"); } else { _handleSubmitted(context, controller.text); } }), ) ], ), ) ], ), ), ), if (voiceRecordingOn) Container( width: 140, height: 140, decoration: BoxDecoration( color: Color.fromRGBO(0, 0, 0, 0.5), borderRadius: BorderRadius.all(Radius.circular(8)), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Icon( Iconfont.yuyin, size: 40, color: Colors.white, ), SignalBars(), ], ), Text( '手指上划,取消发送', style: TextStyle(color: Colors.white, fontSize: 12), ), ], ), ), ], ), ), // ), ); } processMsgList(var list) { return list.map((e) { if (e is String) { return JsonDecoder().convert(e); } return e; }).toList(); } Future _loadMore() async { isAutoJump = false; if (hasMore) { //pge++; await NewApiService().getMsgHistoryList({ "pageNum": _page, "pageSize": 10, // "pageSize":pageSize, "sessionid": widget.id, }, onSuccess: (res) { _page++; print(JsonEncoder().convert(res)); if (res != null && res.length > 0) { res = processMsgList(res); print("初始化数据"); print('res ==== $res'); var ass = []; ass.addAll(res); if (assList.length == 0) { ass.addAll(msgList); assList = ass; } else { ass.addAll(assList); } setState(() { msgList = ass; print('_loadMore msgList ===== $msgList'); hasMore = true; // _jumpBottom(); }); } else { setState(() { hasMore = false; }); } }, onError: (code, msg) { toasts(msg); }); } else { toasts("无更多历史记录!"); } print("加载更多了"); } Future openTheRecorder() async { final permissions = await PermissionHandler() .requestPermissions([PermissionGroup.microphone]); if (permissions[PermissionGroup.microphone] != PermissionStatus.granted) { toasts('需要麦克风权限!'); } var tempDir = await getTemporaryDirectory(); _audioFilePath = '${tempDir.path}/audio_message.aac'; var outputFile = File(_audioFilePath); if (outputFile.existsSync()) { await outputFile.delete(); } await _recorder.openAudioSession(); _recorderIsInited = true; } Future record() async { assert(_recorderIsInited && _player.isStopped); await _recorder.startRecorder( toFile: _audioFilePath, codec: Codec.aacADTS, ); setState(() {}); } Future stopRecorder() async { await _recorder.stopRecorder(); _playbackReady = true; } void play(String fileUrl, Function onFinished) async { assert(_playerIsInited && _playbackReady && _recorder.isStopped && _player.isStopped); await _player.startPlayer( fromURI: fileUrl, codec: Codec.aacADTS, whenFinished: () { setState(() {}); if (onFinished != null) { onFinished(); } }); setState(() {}); } Future stopPlayer() async { await _player.stopPlayer(); } uploadAudioFile(String filePath) async { String uploadName = OssUtil.instance.getImageUploadName(filePath); await NewApiService.uploadImage(context, uploadName, filePath).then((data) { if (data.statusCode == 200) { String str = NewApiUrl.URL_UPLOAD_IMAGE_OSS + "/" + uploadName; print("str:" + str); if (str != null) { Map obj = {"uploadName": uploadName, "success": true}; FastNotification.push("percent", obj); } Provider.of(context, listen: false).sendMessage( context, msg: str, id: widget.id, msgType: 4, dataTable: dataTable, toUserId: widget.toUserId, recordingDuration: recordingDuration); } else { Map obj = {"uploadName": uploadName, "success": false}; FastNotification.push("percent", obj); } }).catchError((data) { Map obj = {"uploadName": uploadName, "success": false}; FastNotification.push("percent", obj); }); } void startCountingRecording() { recordingDuration = 0; if (recordingTimer != null) { recordingTimer.cancel(); } recordingTimer = Timer.periodic(Duration(seconds: 1), (timer) async { if (recordingDuration >= 30) { recordingTimer.cancel(); await stopRecorder(); // await uploadAudioFile(_audioFilePath); setState(() { voiceRecordingOn = false; }); } else { recordingDuration++; } }); } } class SignalBars extends StatefulWidget { @override State createState() => SignalBarsState(); } class SignalBarsState extends State { double bar1Opactity = 0; double bar2Opactity = 0; double bar3Opactity = 0; int loopCount = 0; Timer _timer; @override void initState() { _timer = Timer.periodic(Duration(milliseconds: 400), (timer) { setState(() { if (loopCount == 1) { bar1Opactity = 1; } else if (loopCount == 2) { bar2Opactity = 1; } else if (loopCount == 3) { bar3Opactity = 1; } else { bar1Opactity = 0; bar2Opactity = 0; bar3Opactity = 0; loopCount = 0; } loopCount++; }); }); super.initState(); } @override void dispose() { if (_timer != null) { _timer.cancel(); } super.dispose(); } @override Widget build(BuildContext context) { return Container( height: 35, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 4, width: 18, color: Color.fromRGBO(255, 255, 255, bar3Opactity)), Container( height: 4, width: 14, color: Color.fromRGBO(255, 255, 255, bar2Opactity)), Container( height: 4, width: 10, color: Color.fromRGBO(255, 255, 255, bar1Opactity)), Container( height: 4, width: 6, color: Color.fromRGBO(255, 255, 255, 1)), ], ), ); } }