Hibok
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

784 行
28 KiB

  1. import 'dart:async';
  2. import 'dart:ui';
  3. import 'package:agora_rtc_engine/agora_rtc_engine.dart';
  4. import 'package:cached_network_image/cached_network_image.dart';
  5. import 'package:chat/chat/gift_page.dart';
  6. import 'package:chat/data/UserData.dart';
  7. import 'package:chat/data/WebData.dart';
  8. import 'package:chat/data/constants.dart';
  9. import 'package:chat/generated/i18n.dart';
  10. import 'package:chat/models/UserInfo.dart';
  11. import 'package:chat/models/gift_item_model.dart';
  12. import 'package:chat/models/gift_select_provider.dart';
  13. import 'package:chat/models/money_change.dart';
  14. import 'package:chat/models/ref_name_provider.dart';
  15. import 'package:chat/r.dart';
  16. import 'package:chat/utils/ChargeMoney.dart';
  17. import 'package:chat/utils/CustomUI.dart';
  18. import 'package:chat/utils/HttpUtil.dart';
  19. import 'package:chat/utils/MessageMgr.dart';
  20. import 'package:chat/utils/TokenMgr.dart';
  21. import 'package:chat/utils/anim_effect_overlay.dart';
  22. import 'package:chat/utils/counter_overlay.dart';
  23. import 'package:chat/utils/msgHandler.dart';
  24. import 'package:chat/utils/screen.dart';
  25. import 'package:chat/utils/sound_util.dart';
  26. import 'package:chat/utils/sp_utils.dart';
  27. import 'package:dio/dio.dart';
  28. import 'package:flutter/material.dart';
  29. import 'package:oktoast/oktoast.dart';
  30. import 'package:provider/provider.dart';
  31. class AudioChatPage extends StatefulWidget {
  32. final UserInfo userInfo;
  33. final bool isReplay;
  34. AudioChatPage({this.userInfo, this.isReplay = false});
  35. @override
  36. _AudioChatPageState createState() => _AudioChatPageState();
  37. }
  38. class _AudioChatPageState extends State<AudioChatPage> {
  39. //是否启用扬声器
  40. bool speakPhone = false;
  41. int friendId;
  42. bool isReply; //是否应答模式
  43. bool isChating = false; //是否通话中
  44. //超时挂断
  45. Timer offTimer;
  46. Timer callingTimer;
  47. String channelName;
  48. //打赏礼物信息
  49. List<GiftItemModel> giftList;
  50. final _controller = new PageController();
  51. //礼物数量
  52. int curGiftValue = 1;
  53. bool isQuit = false;
  54. @override
  55. void initState() {
  56. super.initState();
  57. friendId = widget.userInfo.userId;
  58. isReply = widget.isReplay;
  59. MsgHandler.isAudioConnect = true;
  60. getGiftList();
  61. initAgoreSdk();
  62. getDefaultSetting();
  63. //事件监听回调
  64. setAgoreEventListener();
  65. //礼物打赏
  66. MessageMgr().on('Receive Gift', receiveGift);
  67. MessageMgr().on('AudioChat Failed', closeChat);
  68. MessageMgr().on('AudioChat State', refuseAnswer);
  69. }
  70. void getDefaultSetting() async {
  71. bool soundPlayMode =
  72. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  73. setState(() {
  74. speakPhone = soundPlayMode;
  75. });
  76. AgoraRtcEngine.setEnableSpeakerphone(speakPhone);
  77. }
  78. closeChat(args) {
  79. showToast(I18n.of(context).not_online);
  80. _onExit(context);
  81. }
  82. @override
  83. void didChangeDependencies() {
  84. super.didChangeDependencies();
  85. print('test didChangeDependencies');
  86. }
  87. refuseAnswer(args) {
  88. var fdId = args['fdId'];
  89. var isAnswer = args['isAnswer'];
  90. if (fdId == friendId && !isAnswer) {
  91. // showToast('对方暂时无法接听');
  92. _onExit(context);
  93. }
  94. }
  95. receiveGift(gift) {
  96. int giftId = gift.giftId;
  97. int amount = gift.giftAmount;
  98. int total = gift.money;
  99. print(DateTime.now());
  100. var giftModel = giftList[giftId - 1];
  101. print('收到礼物金额:$total');
  102. UserData().incomeMoney += total;
  103. //Provider.of<MoneyChangeProvider>(context, listen: false).addMoney(total);
  104. AnimEffect.showEffect(context, giftId);
  105. AnimEffect.showDashangEffect(context, false, amount, giftModel.name);
  106. }
  107. //本页面即将销毁
  108. @override
  109. void dispose() {
  110. _controller.dispose();
  111. MessageMgr().off('AudioChat State', refuseAnswer);
  112. MessageMgr().off('AudioChat Failed', closeChat);
  113. MessageMgr().off('Receive Gift', receiveGift);
  114. MsgHandler.isAudioConnect = false;
  115. offTimer?.cancel();
  116. callingTimer?.cancel();
  117. SoundUtils().stop();
  118. AgoraRtcEngine.leaveChannel();
  119. //sdk资源释放
  120. AgoraRtcEngine.destroy();
  121. super.dispose();
  122. }
  123. void initAgoreSdk() {
  124. //初始化引擎
  125. AgoraRtcEngine.create(Constants.Agore_appId);
  126. //设置视频为可用 启用音频模块
  127. AgoraRtcEngine.enableAudio();
  128. //每次需要原生视频都要调用_createRendererView
  129. if (!isReply) {
  130. int myId = UserData().basicInfo.userId;
  131. _createRendererView(myId);
  132. }
  133. }
  134. void getGiftList() async {
  135. Map data = {
  136. "userId": UserData().basicInfo.userId,
  137. };
  138. data['sign'] = TokenMgr().getSign(data);
  139. Response res = await HttpUtil().post('prop/get/list', data: data);
  140. if (res == null) {
  141. return;
  142. }
  143. print(res);
  144. Map resData = res.data;
  145. if (resData['code'] == 0) {
  146. //领取成功
  147. giftList = resData['data']
  148. .map<GiftItemModel>((v) => GiftItemModel.fromJson(v))
  149. .toList();
  150. print('giftList length : ${giftList.length}');
  151. }
  152. }
  153. @override
  154. Widget build(BuildContext context) {
  155. return Scaffold(
  156. body: SafeArea(
  157. child: Stack(
  158. children: <Widget>[
  159. ConstrainedBox(
  160. constraints: BoxConstraints.expand(),
  161. child: CachedNetworkImage(
  162. imageUrl: widget.userInfo.headimgurl,
  163. fit: BoxFit.fill,
  164. placeholder: CustomUI.buildImgLoding)),
  165. BackdropFilter(
  166. filter: new ImageFilter.blur(
  167. sigmaX: Screen.width / 8, sigmaY: Screen.width / 8),
  168. child: Container(
  169. color: Colors.black.withOpacity(0.5),
  170. ),
  171. ),
  172. SafeArea(
  173. child: Padding(
  174. padding: EdgeInsets.symmetric(vertical: 40),
  175. child: Column(
  176. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  177. children: <Widget>[
  178. _viewAudio(),
  179. isReply ? _replayToolBar() : _bottomToolBar(),
  180. ],
  181. )))
  182. ],
  183. )));
  184. }
  185. _viewAudio() {
  186. var url = widget.userInfo.headimgurl;
  187. print('url : $url');
  188. print(url.length);
  189. var userName = Provider.of<RefNameProvider>(context)
  190. .getRefName(widget.userInfo.userId, widget.userInfo.nickName);
  191. var local = WebData().getCity(widget.userInfo.city);
  192. var profession = WebData().getProffesionName(widget.userInfo.occupation);
  193. var birth = widget.userInfo.birthday;
  194. print('birth : $birth');
  195. var birthDay = DateTime.parse(birth);
  196. print(birthDay);
  197. var today = DateTime.now();
  198. var testAge = today.difference(birthDay);
  199. print(testAge.inDays);
  200. var age = testAge.inDays ~/ 365;
  201. var connectTip;
  202. if (isChating) {
  203. connectTip = I18n.of(context).chatting;
  204. } else {
  205. if (isReply) {
  206. connectTip = I18n.of(context).voice_msg.replaceFirst('/s1', userName);
  207. } else {
  208. connectTip = I18n.of(context).waitting_answer;
  209. }
  210. }
  211. return Container(
  212. child: Column(
  213. children: <Widget>[
  214. ClipRRect(
  215. borderRadius: BorderRadius.circular(0),
  216. child: url.length == 0
  217. ? SizedBox(
  218. height: 100,
  219. width: 100,
  220. child: Image.asset(R.assetsImagesDefaultNorAvatar))
  221. : CachedNetworkImage(
  222. width: 100,
  223. height: 100,
  224. imageUrl: url,
  225. placeholder: (context, url) => Padding(
  226. padding: EdgeInsets.all(5),
  227. child: CircularProgressIndicator(
  228. valueColor:
  229. AlwaysStoppedAnimation(Constants.BlueTextColor)),
  230. ),
  231. fit: BoxFit.cover,
  232. ),
  233. ),
  234. SizedBox(height: 5),
  235. Row(
  236. mainAxisAlignment: MainAxisAlignment.center,
  237. children: <Widget>[
  238. Container(
  239. constraints: BoxConstraints(maxWidth: Screen.width - 50),
  240. child: Text(
  241. userName,
  242. style: TextStyle(fontSize: 20, color: Colors.white70),
  243. textScaleFactor: 1.0,
  244. overflow: TextOverflow.ellipsis,
  245. textAlign: TextAlign.center,
  246. )),
  247. fixedText(' $age', fontSize: 20, color: Colors.white70)
  248. ],
  249. ),
  250. SizedBox(height: 5),
  251. Text(
  252. '$local',
  253. style: TextStyle(fontSize: 16, color: Colors.white70),
  254. textScaleFactor: 1.0,
  255. textAlign: TextAlign.center,
  256. ),
  257. SizedBox(height: 5),
  258. Text(
  259. '$profession',
  260. style: TextStyle(fontSize: 16, color: Colors.white70),
  261. textScaleFactor: 1.0,
  262. textAlign: TextAlign.center,
  263. ),
  264. SizedBox(height: 20),
  265. Container(
  266. width: Screen.width - 50,
  267. child: Text(
  268. connectTip,
  269. //maxLines: 2,
  270. style: TextStyle(fontSize: 20, color: Colors.white70),
  271. textScaleFactor: 1.0,
  272. textAlign: TextAlign.center,
  273. )),
  274. ],
  275. ),
  276. );
  277. }
  278. _replayToolBar() {
  279. return Container(
  280. child: Row(
  281. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  282. children: <Widget>[
  283. //接听按钮
  284. RawMaterialButton(
  285. onPressed: () {
  286. MsgHandler.stopAudioRing();
  287. int myId = UserData().basicInfo.userId;
  288. MsgHandler.sendReplyAudioChatReq(friendId, true);
  289. _createRendererView(myId);
  290. },
  291. child: Icon(
  292. Icons.call,
  293. color: Colors.white,
  294. size: 35.0,
  295. ),
  296. shape: CircleBorder(),
  297. elevation: 2.0,
  298. fillColor: Colors.greenAccent,
  299. padding: const EdgeInsets.all(15.0),
  300. ),
  301. //挂断按钮
  302. RawMaterialButton(
  303. onPressed: () {
  304. _onExit(context);
  305. MsgHandler.stopAudioRing();
  306. MsgHandler.sendReplyAudioChatReq(friendId, false);
  307. },
  308. child: Icon(
  309. Icons.call_end,
  310. color: Colors.white,
  311. size: 35.0,
  312. ),
  313. shape: CircleBorder(),
  314. elevation: 2.0,
  315. fillColor: Colors.redAccent,
  316. padding: const EdgeInsets.all(15.0),
  317. )
  318. ]));
  319. }
  320. _bottomToolBar() {
  321. List<Widget> showWidgets = [
  322. Text(I18n.of(context).voicing,
  323. textScaleFactor: 1.0,
  324. style: TextStyle(fontSize: 11, color: Colors.white24),
  325. textAlign: TextAlign.center),
  326. SizedBox(height: 10),
  327. Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
  328. Offstage(
  329. offstage: !isChating,
  330. child: UserData().giftSwitch > 0
  331. ? RawMaterialButton(
  332. onPressed: _showGiftSheet,
  333. child: Icon(
  334. IconData(0xe64e, fontFamily: Constants.IconFontFamily),
  335. color: Colors.blueAccent,
  336. size: 20.0,
  337. ),
  338. shape: CircleBorder(),
  339. elevation: 2.0,
  340. fillColor: Colors.white,
  341. padding: const EdgeInsets.all(12.0),
  342. )
  343. : SizedBox(width: 36, height: 36)),
  344. //挂断按钮
  345. RawMaterialButton(
  346. onPressed: () {
  347. MsgHandler.sendReplyAudioChatReq(friendId, false);
  348. _onExit(context);
  349. },
  350. child: Icon(
  351. Icons.call_end,
  352. color: Colors.white,
  353. size: 35.0,
  354. ),
  355. shape: CircleBorder(),
  356. elevation: 2.0,
  357. fillColor: Colors.redAccent,
  358. padding: const EdgeInsets.all(15.0),
  359. ),
  360. //是否外放
  361. Offstage(
  362. offstage: !isChating,
  363. child: RawMaterialButton(
  364. onPressed: _isSpeakPhone,
  365. child: new Icon(
  366. speakPhone ? Icons.volume_up : Icons.volume_down,
  367. color: Colors.blueAccent,
  368. size: 20.0,
  369. ),
  370. shape: new CircleBorder(),
  371. elevation: 2.0,
  372. fillColor: Colors.white,
  373. padding: const EdgeInsets.all(12.0),
  374. ))
  375. ])
  376. ];
  377. if (isChating) {
  378. showWidgets.insert(
  379. 0,
  380. Container(
  381. alignment: Alignment.center,
  382. height: 80,
  383. width: 200,
  384. child: CounterOverlay(),
  385. ));
  386. }
  387. return Container(
  388. child: Column(children: showWidgets),
  389. );
  390. }
  391. //创建渲染视图
  392. void _createRendererView(int uId) {
  393. //增加音频会话对象 为了音频布局需要(通过uid和容器信息)
  394. //加入频道 第一个参数是 token 第二个是频道id 第三个参数 频道信息 一般为空 第四个 用户id
  395. int myId = UserData().basicInfo.userId;
  396. setState(() {
  397. print('加入聊天房间');
  398. var channelName = UserData().getSessionId(friendId).toString();
  399. AgoraRtcEngine.joinChannel(null, channelName, null, myId);
  400. });
  401. }
  402. //设置事件监听
  403. void setAgoreEventListener() {
  404. //成功加入房间
  405. AgoraRtcEngine.onJoinChannelSuccess =
  406. (String channel, int uid, int elapsed) {
  407. print("加入成功id为:$uid elapsed:$elapsed");
  408. if (isReply) {
  409. //回应
  410. setState(() {
  411. isReply = false;
  412. isChating = true;
  413. });
  414. } else {
  415. MsgHandler.sendAudioChatReq(widget.userInfo.userId);
  416. callingTimer = Timer.periodic(Duration(milliseconds: 2200), (timer) {
  417. SoundUtils().play(
  418. 'http://testcyhd.chengyouhd.com/Upload/Audio/even_wheat_sound.mp3',
  419. isLocal: false);
  420. });
  421. //30没人接就自动挂断
  422. offTimer = Timer(Duration(seconds: 30), () {
  423. MsgHandler.sendReplyAudioChatReq(friendId, false);
  424. _onExit(context);
  425. });
  426. }
  427. };
  428. //监听是否有新用户加入
  429. AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {
  430. print("新用户所加入的id为:$uid elapsed:$elapsed");
  431. setState(() {
  432. //更新UI布局
  433. SoundUtils().stop();
  434. callingTimer?.cancel();
  435. isChating = true;
  436. offTimer?.cancel();
  437. });
  438. };
  439. //监听用户是否离开这个房间
  440. AgoraRtcEngine.onUserOffline = (int uid, int reason) {
  441. print("用户离开的id为:$uid");
  442. _onExit(context);
  443. };
  444. AgoraRtcEngine.onError = (dynamic errCode) {
  445. print('通话异常');
  446. // _onExit(context);
  447. };
  448. //监听用户是否离开这个频道
  449. AgoraRtcEngine.onLeaveChannel = () {
  450. print("用户离开");
  451. };
  452. }
  453. void _showGiftSheet() {
  454. showModalBottomSheet(
  455. context: context,
  456. elevation: 2.0,
  457. shape: RoundedRectangleBorder(
  458. borderRadius: BorderRadius.only(
  459. topLeft: Radius.circular(20), topRight: Radius.circular(20))),
  460. backgroundColor: Colors.transparent,
  461. builder: (BuildContext context) {
  462. return StatefulBuilder(
  463. builder: (BuildContext context, setBottomSheetState) {
  464. return Container(
  465. height: 400,
  466. width: Screen.width,
  467. decoration: BoxDecoration(
  468. color: Colors.white,
  469. ),
  470. child: giftList == null
  471. ? Center(
  472. child: Text(
  473. I18n.of(context).no_gift,
  474. textScaleFactor: 1.0,
  475. ))
  476. : Column(
  477. children: <Widget>[
  478. Container(
  479. // decoration: BoxDecoration(
  480. // color: Colors.orangeAccent,
  481. // borderRadius: borderRadius
  482. // ),
  483. padding: EdgeInsets.symmetric(
  484. vertical: 10, horizontal: 20),
  485. child: Row(
  486. children: <Widget>[
  487. Text(I18n.of(context).sent_gift,
  488. textScaleFactor: 1.0,
  489. style: TextStyle(
  490. fontSize: 16,
  491. fontWeight: FontWeight.bold,
  492. color: Constants.BlackTextColor)),
  493. Expanded(
  494. child: SizedBox(),
  495. ),
  496. Image.asset(
  497. R.assetsImagesCoin,
  498. scale: 2,
  499. ),
  500. SizedBox(
  501. width: 5,
  502. ),
  503. fixedText(
  504. I18n.of(context)
  505. .available_balance
  506. .replaceFirst('/s1', ''),
  507. color: Constants.GreyTextColor),
  508. Consumer<MoneyChangeProvider>(
  509. builder: (context,
  510. MoneyChangeProvider counter,
  511. child) =>
  512. fixedText(counter.money.toString(),
  513. color: Color(0xFFDB305D)),
  514. ),
  515. fixedText(I18n.of(context).mask_coin,
  516. color: Color(0xFFDB305D)),
  517. ],
  518. ),
  519. ),
  520. Container(
  521. alignment: Alignment.topCenter,
  522. padding: EdgeInsets.all(10),
  523. height: 230,
  524. child: GiftPage(giftList, 0),
  525. ),
  526. Expanded(child: SizedBox()),
  527. Padding(
  528. padding: EdgeInsets.symmetric(
  529. vertical: 5, horizontal: 20),
  530. child: Row(
  531. children: <Widget>[
  532. Expanded(
  533. child: SizedBox(
  534. height: 36,
  535. child: RaisedButton(
  536. padding:
  537. EdgeInsets.symmetric(horizontal: 5),
  538. child: Text(
  539. I18n.of(context).recharge,
  540. textScaleFactor: 1.0,
  541. maxLines: 1,
  542. style: TextStyle(
  543. fontSize: 16,
  544. fontWeight: FontWeight.bold),
  545. ),
  546. elevation: 2.0,
  547. color: Colors.orangeAccent,
  548. textColor: Colors.white,
  549. onPressed: () {
  550. ChargeMoney.showChargeSheet(context,
  551. () {
  552. setState(() {});
  553. });
  554. },
  555. shape: RoundedRectangleBorder(
  556. borderRadius: BorderRadius.all(
  557. Radius.circular(20)),
  558. ),
  559. ),
  560. )),
  561. SizedBox(width: 20),
  562. Expanded(
  563. flex: 2,
  564. child: Container(
  565. height: 36,
  566. decoration: BoxDecoration(
  567. boxShadow: [
  568. BoxShadow(
  569. blurRadius: 2.0,
  570. color: Colors.white24)
  571. ],
  572. borderRadius: BorderRadius.all(
  573. Radius.circular(20)),
  574. border: Border.all(
  575. color: Colors.red)),
  576. child: Row(
  577. children: <Widget>[
  578. Expanded(
  579. child: DropdownButton(
  580. isExpanded: true,
  581. underline: Container(),
  582. value: curGiftValue,
  583. style: TextStyle(fontSize: 16),
  584. iconEnabledColor:
  585. Colors.redAccent,
  586. onChanged: (newValue) {
  587. setBottomSheetState(() {
  588. curGiftValue = newValue;
  589. });
  590. },
  591. items: <int>[1, 6, 8, 66]
  592. .map<DropdownMenuItem>((v) => DropdownMenuItem(
  593. value: v,
  594. child: Container(
  595. padding:
  596. EdgeInsets.only(
  597. left: 20),
  598. child: fixedText(
  599. v.toString(),
  600. color: v == curGiftValue
  601. ? Colors
  602. .redAccent
  603. : Colors
  604. .black))))
  605. .toList(),
  606. )),
  607. Expanded(
  608. child: InkWell(
  609. child: Container(
  610. decoration: BoxDecoration(
  611. color: Color(0xFFFD6E8F),
  612. borderRadius:
  613. BorderRadius.only(
  614. topRight: Radius
  615. .circular(20),
  616. bottomRight:
  617. Radius.circular(
  618. 20)),
  619. ),
  620. height: 36,
  621. alignment: Alignment.center,
  622. child: Text(
  623. I18n.of(context).give,
  624. textScaleFactor: 1.0,
  625. style: TextStyle(
  626. color: Colors.white,
  627. fontSize: 16,
  628. fontWeight:
  629. FontWeight.bold),
  630. ),
  631. ),
  632. onTap: () {
  633. _sendGift(context);
  634. },
  635. ))
  636. ],
  637. )))
  638. ],
  639. ),
  640. )
  641. ],
  642. ));
  643. },
  644. );
  645. });
  646. }
  647. _sendGift(BuildContext context) async {
  648. int curSelectIndex =
  649. Provider.of<GiftSelectProvider>(context).curSelectIndex;
  650. var curGift = giftList[curSelectIndex];
  651. int price = curGift.price;
  652. int giftId = curGift.id;
  653. int total = curGiftValue * price;
  654. if (total > Provider.of<MoneyChangeProvider>(context).money) {
  655. showToast(I18n.of(context).not_enough);
  656. return;
  657. }
  658. int buyCount = 0;
  659. if (curGiftValue > curGift.amount) {
  660. buyCount = curGiftValue - curGift.amount;
  661. }
  662. Map data = {
  663. "userId": UserData().basicInfo.userId,
  664. "rUserId": friendId,
  665. "id": giftId,
  666. "price": price,
  667. "amount": curGiftValue,
  668. "totalPrice": total,
  669. "payNum": buyCount
  670. };
  671. data['sign'] = TokenMgr().getSign(data);
  672. Response res = await HttpUtil().post('reward/gift', data: data);
  673. if (res == null) {
  674. return;
  675. }
  676. Map resData = res.data;
  677. //showToast(resData['msg']);
  678. if (resData['code'] == 0) {
  679. //赠送成功
  680. print('赠送金额:${resData['data']}');
  681. int receiveAmount = resData['data'];
  682. Navigator.pop(context);
  683. Provider.of<MoneyChangeProvider>(context, listen: false).subMoney(total);
  684. AnimEffect.showEffect(context, giftId);
  685. AnimEffect.showDashangEffect(context, true, curGiftValue, curGift.name);
  686. MsgHandler.sendGiftTo(friendId, giftId, curGiftValue, receiveAmount);
  687. setState(() {});
  688. }
  689. }
  690. //是否开启扬声器
  691. void _isSpeakPhone() {
  692. setState(() {
  693. speakPhone = !speakPhone;
  694. });
  695. AgoraRtcEngine.setEnableSpeakerphone(speakPhone);
  696. }
  697. //退出频道 退出本页面
  698. void _onExit(BuildContext context) {
  699. if (!isQuit) {
  700. if (Navigator.canPop(context)) {
  701. isQuit = true;
  702. Navigator.of(context).pop();
  703. SoundUtils().stop();
  704. callingTimer?.cancel();
  705. }
  706. }
  707. }
  708. }