message_model.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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. );
  176. if (Platform.isAndroid) {
  177. try {
  178. await Getuiflut.initGetuiSdk;
  179. } catch (e) {
  180. print(e);
  181. print("flutter Getuiflut: $e");
  182. }
  183. } else if (Platform.isIOS) {
  184. getuiflut.startSdk(appId: "Rvnote7osU9Ntxk4tpEP46", appKey: "9bHdNmOfPu5KXKAVKKXUv4", appSecret: "aMP4ifBDUJAzvL7KUe2M33");
  185. }
  186. }
  187. Future<void> _showNotification(ReceivedNotification notification) async {
  188. final AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails('101', '趣动', importance: Importance.defaultImportance, priority: Priority.defaultPriority, ticker: '${DateTime.now().millisecondsSinceEpoch}');
  189. final NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);
  190. await flutterLocalNotificationsPlugin.show(notification.id, '${notification.title}', '${notification.body}', platformChannelSpecifics, payload: '${notification.payload}');
  191. }
  192. Future<GameInfoData?> getGame(BuildContext context, int id) async {
  193. GameModel gameModel = GetIt.I<GameModel>();
  194. GameInfoData? game = await gameModel.getGame(id);
  195. return game;
  196. }
  197. _addPushMessage(BuildContext context, Map<String, dynamic> extra) async {
  198. print("_addPushMessage: $extra");
  199. GameInfoData? game = await getGame(context, extra['game_id']);
  200. if (game == null) return;
  201. RespData<UserInfo> user = await GetIt.I<RestClient>().getUserInfo("${extra['from_id']}");
  202. UserInfo? userInfo = user.data;
  203. if (userInfo == null) return;
  204. Map<String, dynamic> args = {
  205. 'invite': {"info": "${extra['invite_info']}", "user": userInfo.toJsonSimple(), "game_id": "${game.id}", "ts": DateTime.now().millisecondsSinceEpoch},
  206. };
  207. print("jpush broadcast: $args");
  208. Broadcast.broadcast("SHOE.SDK.GAME_INVITE", args: args);
  209. if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) {
  210. _showNotification(ReceivedNotification(id: 1, title: "趣动", body: "${userInfo.name}邀请您玩${game.name}", payload: "${userInfo.id}"));
  211. // jpush.sendLocalNotification(LocalNotification(buildId: 1, id: 1, title: "趣动", content: "${userInfo.name}邀请您玩${game.name}", fireTime: DateTime.now()));
  212. } else {
  213. bool _showing = true;
  214. OverlayEntry? entry;
  215. entry = OverlayEntry(builder: (context) {
  216. return Positioned(
  217. //top值,可以改变这个值来改变toast在屏幕中的位置
  218. top: 20,
  219. child: Material(
  220. color: Color(0x00000000),
  221. child: SafeArea(
  222. child: Container(
  223. width: MediaQuery.of(context).size.width,
  224. alignment: Alignment.center, //居中
  225. child: AnimatedOpacity(
  226. //目标透明度
  227. opacity: _showing ? 1.0 : 0.0,
  228. //执行时间
  229. duration: Duration(milliseconds: 1000),
  230. child: Container(
  231. padding: EdgeInsets.all(12.0),
  232. width: double.infinity,
  233. margin: EdgeInsets.symmetric(horizontal: 12.0),
  234. decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Color(0xff241D19).withOpacity(.8)),
  235. child: Row(
  236. children: [
  237. CircleAvatar(
  238. backgroundColor: Colors.black26,
  239. radius: 22,
  240. backgroundImage: userAvatarProvider("${userInfo.avatar}"),
  241. ),
  242. const SizedBox(
  243. width: 12,
  244. ),
  245. Expanded(
  246. child: Column(
  247. crossAxisAlignment: CrossAxisAlignment.start,
  248. children: [
  249. Text(
  250. "${userInfo.name}",
  251. style: TextStyle(color: Colors.white, fontSize: 14),
  252. ),
  253. SizedBox(
  254. height: 5,
  255. ),
  256. Row(
  257. children: [
  258. Text(
  259. "邀请您玩",
  260. style: TextStyle(color: Colors.white, fontSize: 12),
  261. ),
  262. Text(
  263. "${game.name}",
  264. style: TextStyle(color: Theme.of(context).accentColor, fontSize: 12),
  265. ),
  266. ],
  267. )
  268. ],
  269. ),
  270. ),
  271. const SizedBox(
  272. width: 6,
  273. ),
  274. GestureDetector(
  275. onTap: () {
  276. if (entry != null) {
  277. entry!.remove();
  278. }
  279. entry = null;
  280. },
  281. child: Container(
  282. child: Center(
  283. child: Text(
  284. "取消",
  285. style: TextStyle(color: Colors.white, fontSize: 12),
  286. )),
  287. height: 30.0,
  288. width: 60.0,
  289. decoration: BoxDecoration(
  290. border: Border.all(
  291. color: Colors.white,
  292. width: 1,
  293. ),
  294. borderRadius: BorderRadius.circular(50)),
  295. ),
  296. ),
  297. const SizedBox(
  298. width: 6,
  299. ),
  300. PrimaryButton(
  301. callback: () {
  302. if (entry != null) {
  303. _showing = false;
  304. if (!_showing) {
  305. entry!.remove();
  306. }
  307. }
  308. entry = null;
  309. startGame(context, game, invite: "${extra['invite_info']}", user: userInfo, skipGuide: true);
  310. },
  311. content: "继续",
  312. height: 30.0,
  313. width: 60.0,
  314. fontSize: 12,
  315. )
  316. ],
  317. ),
  318. ),
  319. )),
  320. ),
  321. ));
  322. });
  323. if (entry != null) Overlay.of(context)?.insert(entry!);
  324. await Future.delayed(Duration(milliseconds: 5000));
  325. if (entry != null) {
  326. _showing = false;
  327. //重新绘制UI,类似setState
  328. entry!.markNeedsBuild();
  329. //等待动画执行
  330. await Future.delayed(Duration(milliseconds: 1000));
  331. if (!_showing) {
  332. entry!.remove();
  333. }
  334. }
  335. entry = null;
  336. }
  337. }
  338. loop(BuildContext context) async {
  339. close();
  340. if (inProduction) {
  341. if (openSocial())
  342. periodicSubscription = Stream.periodic(Duration(seconds: 10)).listen((event) {
  343. if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) return;
  344. loopMessage(context);
  345. });
  346. countPeriodicSubscription = Stream.periodic(Duration(seconds: 60)).listen((event) {
  347. _loopCount();
  348. });
  349. selfId = "${Provider.of<UserModel>(context, listen: false).user.id}";
  350. _loopCount();
  351. }
  352. }
  353. loopMessage(BuildContext context) async {
  354. Message? data;
  355. SharedPreferences prefs = await SharedPreferences.getInstance();
  356. String? token = prefs.getString("token");
  357. if (token == null || token.isEmpty == true) return;
  358. if (_curId != null) {
  359. data = (await api.getMessageForPoll(curId: _curId)).data;
  360. } else {
  361. data = (await api.getMessageForPoll()).data;
  362. }
  363. if (data == null) return;
  364. // curId 这里一定是接口返回的 本地存 sqlite的 curId 全部都是 0
  365. _curId = data.curId;
  366. List<MessageInstance> messages = data.messages ?? [];
  367. // 没有新消息还处理流程吗?
  368. // if(_messages.length == 0)
  369. // return
  370. // var list = await MessageDB().findAll(); // 清空数字
  371. // print("[list]:$list--------------------------");
  372. // var userList = await MessageDB().findAllUser();
  373. // print("[list]:$userList--------------------------");
  374. // MessageDB().deleteTable(); //
  375. // 这里本来是做了去重操作的 现在不用了 ...
  376. // if (_messages.length > 0) {
  377. // var list = await MessageDB().findLatest();
  378. // print(list);
  379. // if (list.length != 0) {
  380. // int lastCurId = list[0]["curId"];
  381. // print('[lastCurId]:$lastCurId-------------------------');
  382. // // 可能会重复插入...;
  383. // if (lastCurId != _curId) {
  384. // add(data.messages, _curId);
  385. // }
  386. // } else {
  387. //// add(data.messages, _curId);
  388. // }
  389. // }
  390. int me = Provider.of<UserModel>(context, listen: false).user.id;
  391. _add(me, messages);
  392. }
  393. add(BuildContext context, List<MessageInstance> messages) async {
  394. int me = Provider.of<UserModel>(context, listen: false).user.id;
  395. _add(me, messages);
  396. }
  397. _add(int me, List<MessageInstance> messages) async {
  398. if (messages.isEmpty == true) return;
  399. List<Map<String, dynamic>> _items = [];
  400. int messageCount = 0;
  401. for (MessageInstance _instance in messages) {
  402. bool isMe = me == _instance.fromUser?.id;
  403. MessageUser? other = isMe ? _instance.toUser : _instance.fromUser;
  404. if (other == null) continue;
  405. var list = await MessageDB().findHasUserId(me, other.id!);
  406. int chatId = 0;
  407. if (list.length == 0) {
  408. chatId = await MessageDB().createChatUser(me, other);
  409. } else {
  410. var dbUser = list[0];
  411. chatId = dbUser['id'];
  412. if (other.name != dbUser['name'] || other.avatar != dbUser['avatar']) {
  413. MessageDB().updateChatUser(chatId, other);
  414. }
  415. }
  416. Map<String, dynamic> map = _instance.toDBJson();
  417. map['chat_id'] = chatId;
  418. // 插进去是 0: 已读, 1: 未读
  419. map['status'] = isMe ? 0 : 1;
  420. _items.add(map);
  421. messageCount += isMe ? 0 : 1;
  422. }
  423. // print("1111111111111111111 add message $_items");
  424. await MessageDB().insertAll(_items);
  425. notifierMessage.value = notifierMessage.value + messageCount;
  426. if (messages.isNotEmpty == true) _queryController.add(messages.length);
  427. }
  428. close() {
  429. ///关闭
  430. periodicSubscription?.cancel();
  431. countPeriodicSubscription?.cancel();
  432. // _queryController.close();
  433. }
  434. ValueNotifier<int> notifierComment = ValueNotifier(0);
  435. ValueNotifier<int> notifierLike = ValueNotifier(0);
  436. ValueNotifier<int> notifierSocial = ValueNotifier(0);
  437. ValueNotifier<int> notifierMessage = ValueNotifier(0);
  438. ValueNotifier<int> notifierFriendFansList = ValueNotifier(0);
  439. ValueNotifier<int> notifierFriendFollowList = ValueNotifier(0);
  440. ValueNotifier<int> notifierFriend = ValueNotifier(0);
  441. ValueNotifier<int> notifierSocialTotal = ValueNotifier(0);
  442. //
  443. // getSocialCount() async {
  444. // int count = 0;
  445. // try {
  446. // int _count = (await api.getNoticeCount("comment", "0")).data ?? 0;
  447. // notifierComment.value = _count;
  448. // count += _count;
  449. // } catch (e) {
  450. // print(e);
  451. // }
  452. // try {
  453. // int _count = (await api.getNoticeCount("like", "0")).data ?? 0;
  454. // notifierLike.value = _count;
  455. // count += _count;
  456. // } catch (e) {
  457. // print(e);
  458. // }
  459. //
  460. // List<int> result = messag
  461. //
  462. // notifierSocial.value = count + unRead;
  463. // }
  464. //
  465. // getFriendCount() async {
  466. // int count = 0;
  467. // // try {
  468. // // FriendList? list = (await api.userFollowList(limit: 1)).data;
  469. // // int _count = 0;
  470. // // list?.list?.forEach((element) {
  471. // // if(element.isIgnore == 0 && element.isFriends == "0")
  472. // // _count += 1;
  473. // // });
  474. // // notifierFriendFollowList.value = _count;
  475. // // count += _count;
  476. // // } catch (e) {
  477. // // print(e);
  478. // // }
  479. // try {
  480. // // FriendList? list = (await api.userFansList(limit: 1)).data;
  481. // // int _count = 0;
  482. // // list?.list?.forEach((element) {
  483. // // if(element.isIgnore == 0 && element.isFriends == "0")
  484. // // _count += 1;
  485. // // });
  486. // int _count = (await api.userFansList(limit: 1)).data?.noIgnoreCount ?? 0;
  487. // notifierFriendFansList.value = _count;
  488. // count += _count;
  489. // } catch (e) {
  490. // print(e);
  491. // }
  492. // notifierFriend.value = count;
  493. // }
  494. void getCount() async {
  495. List<int> result = await mergeMessageCount();
  496. // int count = result.fold(0, (previousValue, element) => previousValue + element);
  497. notifierLike.value = result[0];
  498. notifierComment.value = result[1];
  499. notifierFriendFansList.value = result[2];
  500. int unRead = 0;
  501. if (selfId != null) {
  502. var unReadList = await MessageDB().getMessageUnRead(selfId!);
  503. unRead = unReadList.fold(0, (previousValue, element) => previousValue + (element["cout"] as int));
  504. notifierMessage.value = unRead;
  505. }
  506. notifierSocial.value = result[0] + result[1] + unRead;
  507. notifierFriend.value = result[2];
  508. }
  509. void _loopCount() {
  510. SharedPreferences.getInstance().then((value) {
  511. if (value.containsKey("token")) {
  512. getCount();
  513. }
  514. });
  515. }
  516. void init() {
  517. notifierSocial.addListener(() {
  518. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  519. });
  520. notifierFriend.addListener(() {
  521. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  522. });
  523. notifierMessage.addListener(() {
  524. notifierSocial.value = notifierComment.value + notifierLike.value + notifierMessage.value;
  525. notifierSocialTotal.value = notifierSocial.value + notifierFriend.value;
  526. });
  527. if (inProduction && openSocial()) {
  528. _loopCount();
  529. }
  530. Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
  531. connectivityResult = result;
  532. });
  533. }
  534. }