message_model.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:broadcast/broadcast.dart';
  5. import 'package:connectivity_plus/connectivity_plus.dart';
  6. import 'package:flutter/cupertino.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message;
  10. import 'package:get_it/get_it.dart';
  11. import 'package:getuiflut/getuiflut.dart';
  12. import 'package:provider/provider.dart';
  13. import 'package:rxdart/subjects.dart';
  14. import 'package:shared_preferences/shared_preferences.dart';
  15. import 'package:sport/application.dart';
  16. import 'package:sport/bean/game.dart';
  17. import 'package:sport/bean/message.dart';
  18. import 'package:sport/bean/user_info.dart';
  19. import 'package:sport/db/message_db.dart';
  20. import 'package:sport/pages/game/game_detail.dart';
  21. import 'package:sport/pages/social/chat_page.dart';
  22. import 'package:sport/provider/game_model.dart';
  23. import 'package:sport/provider/user_model.dart';
  24. import 'package:sport/router/navigator_util.dart';
  25. import 'package:sport/services/api/inject_api.dart';
  26. import 'package:sport/services/api/request.dart';
  27. import 'package:sport/services/api/resp.dart';
  28. import 'package:sport/services/api/rest_client.dart';
  29. import 'package:sport/widgets/button_primary.dart';
  30. import 'package:sport/widgets/image.dart';
  31. final BehaviorSubject<ReceivedNotification> didReceiveLocalNotificationSubject = BehaviorSubject<ReceivedNotification>();
  32. final BehaviorSubject<String?> selectNotificationSubject = BehaviorSubject<String?>();
  33. const MethodChannel platform = MethodChannel('dexterx.dev/flutter_local_notifications_example');
  34. class ReceivedNotification {
  35. ReceivedNotification({
  36. required this.id,
  37. required this.title,
  38. required this.body,
  39. required this.payload,
  40. });
  41. final int id;
  42. final String? title;
  43. final String? body;
  44. final String? payload;
  45. }
  46. class MessageModel extends ChangeNotifier with InjectApi {
  47. // Message _message;
  48. int? _curId;
  49. AppLifecycleState? state;
  50. String? selfId;
  51. final StreamController<int> _queryController = StreamController.broadcast();
  52. Stream<int> get queryStream => _queryController.stream;
  53. ///事件订阅对象
  54. StreamSubscription? periodicSubscription, countPeriodicSubscription;
  55. final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  56. push(BuildContext context) async {
  57. var user = Provider.of<UserModel>(context, listen: false);
  58. var userId = user.user.id;
  59. print("flutter push user $userId");
  60. if (userId == 0) return;
  61. final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
  62. final IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings(
  63. requestAlertPermission: false,
  64. requestBadgePermission: false,
  65. requestSoundPermission: false,
  66. onDidReceiveLocalNotification: (int id, String? title, String? body, String? payload) async {
  67. didReceiveLocalNotificationSubject.add(ReceivedNotification(id: id, title: title, body: body, payload: payload));
  68. });
  69. final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
  70. await flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: (String? payload) async {
  71. if (payload != null) {
  72. debugPrint('notification payload: $payload');
  73. UserInfo? info = (await api.getUserInfo("$payload")).data;
  74. if (info != null)
  75. await NavigatorUtil.goPage(
  76. context,
  77. (context) => ChatPage(
  78. info,
  79. fetch: true,
  80. ));
  81. }
  82. selectNotificationSubject.add(payload);
  83. });
  84. //
  85. // if (Platform.isAndroid) {
  86. // await FlutterPluginMipush.init(appId: "2882303761520111715", appKey: "5722011155715");
  87. // } else if (Platform.isIOS) {
  88. // await FlutterPluginMipush.init(appId: "2882303761520111481", appKey: "5942011177481·");
  89. // } else {
  90. // return;
  91. // }
  92. //
  93. // await FlutterPluginMipush.setAlias(alias: "$userId", category: "app");
  94. // FlutterPluginMipush.stream.listen((event) {
  95. // print("FlutterPluginMipush listen $event");
  96. // Map<String, dynamic> extra = json.decode(event);
  97. // _addPushMessage(context, extra);
  98. // });
  99. print("flutter initGetuiSdk");
  100. var getuiflut = Getuiflut();
  101. getuiflut.addEventHandler(
  102. // 注册收到 cid 的回调
  103. onReceiveClientId: (String message) async {
  104. print("flutter onReceiveClientId: $message");
  105. getuiflut.bindAlias("$userId", "app");
  106. },
  107. // 注册 DeviceToken 回调
  108. onRegisterDeviceToken: (String message) async {
  109. print("flutter onRegisterDeviceToken: $message");
  110. },
  111. // SDK收到透传消息回调
  112. onReceivePayload: (Map<String, dynamic> message) async {
  113. print("flutter onReceivePayload: $message");
  114. if (message.containsKey("payloadMsg")) {
  115. try {
  116. _addPushMessage(context, json.decode(message["payloadMsg"]));
  117. } catch (e) {
  118. print(e);
  119. }
  120. }
  121. },
  122. // 点击通知回调
  123. onReceiveNotificationResponse: (Map<String, dynamic> message) async {
  124. print("flutter onReceiveNotificationResponse: $message");
  125. },
  126. // APPLink中携带的透传payload信息
  127. onAppLinkPayload: (String message) async {},
  128. //通知服务开启\关闭回调
  129. onPushModeResult: (Map<String, dynamic> message) async {
  130. print("flutter onPushModeResult: $message");
  131. },
  132. // SetTag回调
  133. onSetTagResult: (Map<String, dynamic> message) async {
  134. print("flutter onSetTagResult: $message");
  135. },
  136. //设置别名回调
  137. onAliasResult: (Map<String, dynamic> message) async {
  138. print("flutter onAliasResult: $message");
  139. },
  140. //查询别名回调
  141. onQueryTagResult: (Map<String, dynamic> message) async {
  142. print("flutter onQueryTagResult: $message");
  143. },
  144. //APNs通知即将展示回调
  145. onWillPresentNotification: (Map<String, dynamic> message) async {
  146. print("flutter onWillPresentNotification: $message");
  147. },
  148. //APNs通知设置跳转回调
  149. onOpenSettingsForNotification: (Map<String, dynamic> message) async {
  150. print("flutter onOpenSettingsForNotification: $message");
  151. },
  152. onReceiveMessageData: (Map<String, dynamic> event) async {
  153. print("flutter onReceiveMessageData: $event");
  154. if (event.containsKey("payload")) {
  155. try {
  156. _addPushMessage(context, json.decode(event["payload"]));
  157. } catch (e) {
  158. print(e);
  159. }
  160. }
  161. },
  162. onNotificationMessageClicked: (Map<String, dynamic> event) async {
  163. print("flutter onNotificationMessageClicked: $event");
  164. if (event.containsKey("payload")) {
  165. try {
  166. _addPushMessage(context, json.decode(event["payload"]));
  167. } catch (e) {
  168. print(e);
  169. }
  170. }
  171. },
  172. onNotificationMessageArrived: (Map<String, dynamic> event) async {
  173. print("flutter onNotificationMessageArrived: $event");
  174. },
  175. // onGrantAuthorization: (String res) async { },
  176. // onTransmitUserMessageReceive: (Map<String, dynamic> event) async{ },
  177. );
  178. if (Platform.isAndroid) {
  179. try {
  180. await Getuiflut.initGetuiSdk;
  181. } catch (e) {
  182. print(e);
  183. print("flutter Getuiflut: $e");
  184. }
  185. } else if (Platform.isIOS) {
  186. getuiflut.startSdk(appId: "Rvnote7osU9Ntxk4tpEP46", appKey: "9bHdNmOfPu5KXKAVKKXUv4", appSecret: "aMP4ifBDUJAzvL7KUe2M33");
  187. }
  188. }
  189. Future<void> _showNotification(ReceivedNotification notification) async {
  190. final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails('101', '趣动', importance: Importance.defaultImportance, priority: Priority.defaultPriority, ticker: '${DateTime.now().millisecondsSinceEpoch}');
  191. final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);
  192. await flutterLocalNotificationsPlugin.show(notification.id, '${notification.title}', '${notification.body}', platformChannelSpecifics, payload: '${notification.payload}');
  193. }
  194. Future<GameInfoData?> getGame(BuildContext context, int id) async {
  195. GameModel gameModel = GetIt.I<GameModel>();
  196. GameInfoData? game = await gameModel.getGame(id);
  197. return game;
  198. }
  199. _addPushMessage(BuildContext context, Map<String, dynamic> extra) async {
  200. print("_addPushMessage: $extra");
  201. GameInfoData? game = await getGame(context, extra['game_id']);
  202. if (game == null) return;
  203. RespData<UserInfo> user = await GetIt.I<RestClient>().getUserInfo("${extra['from_id']}");
  204. UserInfo? userInfo = user.data;
  205. if (userInfo == null) return;
  206. Map<String, dynamic> args = {
  207. 'invite': {"info": "${extra['invite_info']}", "user": userInfo.toJsonSimple(), "game_id": "${game.id}", "ts": DateTime.now().millisecondsSinceEpoch},
  208. };
  209. print("jpush broadcast: $args");
  210. Broadcast.broadcast("SHOE.SDK.GAME_INVITE", args: args);
  211. if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) {
  212. _showNotification(ReceivedNotification(id: 1, title: "趣动", body: "${userInfo.name}邀请您玩${game.name}", payload: "${userInfo.id}"));
  213. // jpush.sendLocalNotification(LocalNotification(buildId: 1, id: 1, title: "趣动", content: "${userInfo.name}邀请您玩${game.name}", fireTime: DateTime.now()));
  214. } else {
  215. bool _showing = true;
  216. OverlayEntry? entry;
  217. entry = OverlayEntry(builder: (context) {
  218. return Positioned(
  219. //top值,可以改变这个值来改变toast在屏幕中的位置
  220. top: 20,
  221. child: Material(
  222. color: Color(0x00000000),
  223. child: SafeArea(
  224. child: Container(
  225. width: MediaQuery.of(context).size.width,
  226. alignment: Alignment.center, //居中
  227. child: AnimatedOpacity(
  228. //目标透明度
  229. opacity: _showing ? 1.0 : 0.0,
  230. //执行时间
  231. duration: Duration(milliseconds: 1000),
  232. child: Container(
  233. padding: EdgeInsets.all(12.0),
  234. width: double.infinity,
  235. margin: EdgeInsets.symmetric(horizontal: 12.0),
  236. decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Color(0xff241D19).withOpacity(.8)),
  237. child: Row(
  238. children: [
  239. CircleAvatar(
  240. backgroundColor: Colors.black26,
  241. radius: 22,
  242. backgroundImage: userAvatarProvider("${userInfo.avatar}"),
  243. ),
  244. const SizedBox(
  245. width: 12,
  246. ),
  247. Expanded(
  248. child: Column(
  249. crossAxisAlignment: CrossAxisAlignment.start,
  250. children: [
  251. Text(
  252. "${userInfo.name}",
  253. style: TextStyle(color: Colors.white, fontSize: 14),
  254. ),
  255. SizedBox(
  256. height: 5,
  257. ),
  258. Row(
  259. children: [
  260. Text(
  261. "邀请您玩",
  262. style: TextStyle(color: Colors.white, fontSize: 12),
  263. ),
  264. Text(
  265. "${game.name}",
  266. style: TextStyle(color: Theme.of(context).accentColor, fontSize: 12),
  267. ),
  268. ],
  269. )
  270. ],
  271. ),
  272. ),
  273. const SizedBox(
  274. width: 6,
  275. ),
  276. GestureDetector(
  277. onTap: () {
  278. if (entry != null) {
  279. entry!.remove();
  280. }
  281. entry = null;
  282. },
  283. child: Container(
  284. child: Center(
  285. child: Text(
  286. "取消",
  287. style: TextStyle(color: Colors.white, fontSize: 12),
  288. )),
  289. height: 30.0,
  290. width: 60.0,
  291. decoration: BoxDecoration(
  292. border: Border.all(
  293. color: Colors.white,
  294. width: 1,
  295. ),
  296. borderRadius: BorderRadius.circular(50)),
  297. ),
  298. ),
  299. const SizedBox(
  300. width: 6,
  301. ),
  302. PrimaryButton(
  303. callback: () {
  304. if (entry != null) {
  305. _showing = false;
  306. if (!_showing) {
  307. entry!.remove();
  308. }
  309. }
  310. entry = null;
  311. startGame(context, game, invite: "${extra['invite_info']}", user: userInfo, skipGuide: true);
  312. },
  313. content: "继续",
  314. height: 30.0,
  315. width: 60.0,
  316. fontSize: 12,
  317. )
  318. ],
  319. ),
  320. ),
  321. )),
  322. ),
  323. ));
  324. });
  325. if (entry != null) Overlay.of(context)?.insert(entry!);
  326. await Future.delayed(Duration(milliseconds: 5000));
  327. if (entry != null) {
  328. _showing = false;
  329. //重新绘制UI,类似setState
  330. entry!.markNeedsBuild();
  331. //等待动画执行
  332. await Future.delayed(Duration(milliseconds: 1000));
  333. if (!_showing) {
  334. entry!.remove();
  335. }
  336. }
  337. entry = null;
  338. }
  339. }
  340. loop(BuildContext context) async {
  341. close();
  342. if (inProduction) {
  343. if (openSocial())
  344. periodicSubscription = Stream.periodic(Duration(seconds: 10)).listen((event) {
  345. if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) return;
  346. loopMessage(context);
  347. });
  348. countPeriodicSubscription = Stream.periodic(Duration(seconds: 60)).listen((event) {
  349. _loopCount();
  350. });
  351. selfId = "${Provider.of<UserModel>(context, listen: false).user.id}";
  352. _loopCount();
  353. }
  354. }
  355. loopMessage(BuildContext context) async {
  356. Message? data;
  357. SharedPreferences prefs = await SharedPreferences.getInstance();
  358. String? token = prefs.getString("token");
  359. if (token == null || token.isEmpty == true) return;
  360. if (_curId != null) {
  361. data = (await api.getMessageForPoll(curId: _curId)).data;
  362. } else {
  363. data = (await api.getMessageForPoll()).data;
  364. }
  365. if (data == null) return;
  366. // curId 这里一定是接口返回的 本地存 sqlite的 curId 全部都是 0
  367. _curId = data.curId;
  368. List<MessageInstance> messages = data.messages ?? [];
  369. // 没有新消息还处理流程吗?
  370. // if(_messages.length == 0)
  371. // return
  372. // var list = await MessageDB().findAll(); // 清空数字
  373. // print("[list]:$list--------------------------");
  374. // var userList = await MessageDB().findAllUser();
  375. // print("[list]:$userList--------------------------");
  376. // MessageDB().deleteTable(); //
  377. // 这里本来是做了去重操作的 现在不用了 ...
  378. // if (_messages.length > 0) {
  379. // var list = await MessageDB().findLatest();
  380. // print(list);
  381. // if (list.length != 0) {
  382. // int lastCurId = list[0]["curId"];
  383. // print('[lastCurId]:$lastCurId-------------------------');
  384. // // 可能会重复插入...;
  385. // if (lastCurId != _curId) {
  386. // add(data.messages, _curId);
  387. // }
  388. // } else {
  389. //// add(data.messages, _curId);
  390. // }
  391. // }
  392. int me = Provider.of<UserModel>(context, listen: false).user.id;
  393. _add(me, messages);
  394. }
  395. add(BuildContext context, List<MessageInstance> messages) async {
  396. int me = Provider.of<UserModel>(context, listen: false).user.id;
  397. _add(me, messages);
  398. }
  399. _add(int me, List<MessageInstance> messages) async {
  400. if (messages.isEmpty == true) return;
  401. List<Map<String, dynamic>> _items = [];
  402. int messageCount = 0;
  403. for (MessageInstance _instance in messages) {
  404. bool isMe = me == _instance.fromUser?.id;
  405. MessageUser? other = isMe ? _instance.toUser : _instance.fromUser;
  406. if (other == null) continue;
  407. var list = await MessageDB().findHasUserId(me, other.id!);
  408. int chatId = 0;
  409. if (list.length == 0) {
  410. chatId = await MessageDB().createChatUser(me, other);
  411. } else {
  412. var dbUser = list[0];
  413. chatId = dbUser['id'];
  414. if (other.name != dbUser['name'] || other.avatar != dbUser['avatar']) {
  415. MessageDB().updateChatUser(chatId, other);
  416. }
  417. }
  418. Map<String, dynamic> map = _instance.toDBJson();
  419. map['chat_id'] = chatId;
  420. // 插进去是 0: 已读, 1: 未读
  421. map['status'] = isMe ? 0 : 1;
  422. _items.add(map);
  423. messageCount += isMe ? 0 : 1;
  424. }
  425. // print("1111111111111111111 add message $_items");
  426. await MessageDB().insertAll(_items);
  427. notifierMessage.value = notifierMessage.value + messageCount;
  428. if (messages.isNotEmpty == true) _queryController.add(messages.length);
  429. }
  430. close() {
  431. ///关闭
  432. periodicSubscription?.cancel();
  433. countPeriodicSubscription?.cancel();
  434. // _queryController.close();
  435. }
  436. ValueNotifier<int> notifierComment = ValueNotifier(0);
  437. ValueNotifier<int> notifierLike = ValueNotifier(0);
  438. ValueNotifier<int> notifierSocial = ValueNotifier(0);
  439. ValueNotifier<int> notifierMessage = ValueNotifier(0);
  440. ValueNotifier<int> notifierFriendFansList = ValueNotifier(0);
  441. ValueNotifier<int> notifierFriendFollowList = ValueNotifier(0);
  442. ValueNotifier<int> notifierFriend = ValueNotifier(0);
  443. ValueNotifier<int> notifierSocialTotal = ValueNotifier(0);
  444. //
  445. // getSocialCount() async {
  446. // int count = 0;
  447. // try {
  448. // int _count = (await api.getNoticeCount("comment", "0")).data ?? 0;
  449. // notifierComment.value = _count;
  450. // count += _count;
  451. // } catch (e) {
  452. // print(e);
  453. // }
  454. // try {
  455. // int _count = (await api.getNoticeCount("like", "0")).data ?? 0;
  456. // notifierLike.value = _count;
  457. // count += _count;
  458. // } catch (e) {
  459. // print(e);
  460. // }
  461. //
  462. // List<int> result = messag
  463. //
  464. // notifierSocial.value = count + unRead;
  465. // }
  466. //
  467. // getFriendCount() async {
  468. // int count = 0;
  469. // // try {
  470. // // FriendList? list = (await api.userFollowList(limit: 1)).data;
  471. // // int _count = 0;
  472. // // list?.list?.forEach((element) {
  473. // // if(element.isIgnore == 0 && element.isFriends == "0")
  474. // // _count += 1;
  475. // // });
  476. // // notifierFriendFollowList.value = _count;
  477. // // count += _count;
  478. // // } catch (e) {
  479. // // print(e);
  480. // // }
  481. // try {
  482. // // FriendList? list = (await api.userFansList(limit: 1)).data;
  483. // // int _count = 0;
  484. // // list?.list?.forEach((element) {
  485. // // if(element.isIgnore == 0 && element.isFriends == "0")
  486. // // _count += 1;
  487. // // });
  488. // int _count = (await api.userFansList(limit: 1)).data?.noIgnoreCount ?? 0;
  489. // notifierFriendFansList.value = _count;
  490. // count += _count;
  491. // } catch (e) {
  492. // print(e);
  493. // }
  494. // notifierFriend.value = count;
  495. // }
  496. void getCount() async {
  497. List<int> result = await mergeMessageCount();
  498. // int count = result.fold(0, (previousValue, element) => previousValue + element);
  499. notifierLike.value = result[0];
  500. notifierComment.value = result[1];
  501. notifierFriendFansList.value = result[2];
  502. int unRead = 0;
  503. if (selfId != null) {
  504. var unReadList = await MessageDB().getMessageUnRead(selfId!);
  505. unRead = unReadList.fold(0, (previousValue, element) => previousValue + (element["cout"] as int));
  506. notifierMessage.value = unRead;
  507. }
  508. notifierSocial.value = result[0] + result[1] + unRead;
  509. notifierFriend.value = result[2];
  510. }
  511. void _loopCount() {
  512. SharedPreferences.getInstance().then((value) {
  513. if (value.containsKey("token")) {
  514. getCount();
  515. }
  516. });
  517. }
  518. void init() {
  519. notifierSocial.addListener(() {
  520. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  521. });
  522. notifierFriend.addListener(() {
  523. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  524. });
  525. notifierMessage.addListener(() {
  526. notifierSocial.value = notifierComment.value + notifierLike.value + notifierMessage.value;
  527. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  528. });
  529. if (inProduction && openSocial()) {
  530. _loopCount();
  531. }
  532. Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
  533. connectivityResult = result;
  534. });
  535. }
  536. }