From 1b13a90615ae86d0d3a217bc74dfbb30d1184170 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Sat, 18 Apr 2020 14:14:54 +0200 Subject: [PATCH] Use Websocket to update number of unread notifications --- lib/helpers/events_helper.dart | 7 ++++ lib/helpers/websocket_helper.dart | 35 +++++++++++++++++- lib/models/count_unread_notifications.dart | 4 +- lib/models/ws_message.dart | 25 +++++++++++++ lib/ui/widgets/navbar_widget.dart | 43 ++++++++++++---------- lib/ui/widgets/safe_state.dart | 16 ++++++-- 6 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 lib/models/ws_message.dart diff --git a/lib/helpers/events_helper.dart b/lib/helpers/events_helper.dart index 287b397..d9c7984 100644 --- a/lib/helpers/events_helper.dart +++ b/lib/helpers/events_helper.dart @@ -9,6 +9,13 @@ import 'package:event_bus/event_bus.dart'; /// Main WebSocket closed class WSClosedEvent {} +/// New number of notifications +class NewNumberNotifsEvent { + final int newNum; + + NewNumberNotifsEvent(this.newNum); +} + class EventsHelper { static EventBus _mgr = EventBus(); diff --git a/lib/helpers/websocket_helper.dart b/lib/helpers/websocket_helper.dart index fde8abb..437242b 100644 --- a/lib/helpers/websocket_helper.dart +++ b/lib/helpers/websocket_helper.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; + import 'package:comunic/helpers/events_helper.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/config.dart'; +import 'package:comunic/models/ws_message.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; /// User web socket helper @@ -40,7 +43,10 @@ class WebSocketHelper { _ws.stream.listen( // When we got data - (onData) => print("WS New data: $onData"), + (data) { + print("WS New data: $data"); + _processMessage(data.toString()); + }, // Print errors on console onError: (e, stack) { @@ -55,4 +61,31 @@ class WebSocketHelper { }, ); } + + /// Process incoming message + static _processMessage(String msgStr) { + try { + final msg = WsMessage.fromJSON(jsonDecode(msgStr)); + + if (!msg.hasId) + _processUnattendedMessage(msg); + else + throw Exception("Do not know how to process attended message!"); + } catch (e, stack) { + print("WS could not process message: $e"); + print(stack); + } + } + + /// Process an unattended message + static _processUnattendedMessage(WsMessage msg) { + switch (msg.title) { + case "number_notifs": + EventsHelper.emit(NewNumberNotifsEvent(msg.data)); + break; + + default: + throw Exception("Unknown message type: ${msg.title}"); + } + } } diff --git a/lib/models/count_unread_notifications.dart b/lib/models/count_unread_notifications.dart index 140a265..5b263c0 100644 --- a/lib/models/count_unread_notifications.dart +++ b/lib/models/count_unread_notifications.dart @@ -3,8 +3,8 @@ /// @author Pierre Hubert class CountUnreadNotifications { - final int notifications; - final int conversations; + int notifications; + int conversations; CountUnreadNotifications({ this.notifications, diff --git a/lib/models/ws_message.dart b/lib/models/ws_message.dart new file mode 100644 index 0000000..a40896b --- /dev/null +++ b/lib/models/ws_message.dart @@ -0,0 +1,25 @@ +import 'package:flutter/widgets.dart'; + +/// WebSocket message +/// +/// @author Pierre Hubert + +class WsMessage { + final String id; + final String title; + final dynamic data; + + const WsMessage({ + @required this.id, + @required this.title, + @required this.data, + }) : assert(id != null), + assert(title != null); + + /// Construct a message from a JSON document (messages coming from the server) + static WsMessage fromJSON(final Map m) { + return WsMessage(id: m["id"], title: m["title"], data: m["data"]); + } + + bool get hasId => this.id != null && this.id.isNotEmpty; +} diff --git a/lib/ui/widgets/navbar_widget.dart b/lib/ui/widgets/navbar_widget.dart index 25825d6..7846b62 100644 --- a/lib/ui/widgets/navbar_widget.dart +++ b/lib/ui/widgets/navbar_widget.dart @@ -1,3 +1,4 @@ +import 'package:comunic/helpers/events_helper.dart'; import 'package:comunic/helpers/notifications_helper.dart'; import 'package:comunic/models/count_unread_notifications.dart'; import 'package:comunic/ui/widgets/safe_state.dart'; @@ -113,8 +114,7 @@ class ComunicAppBar extends StatefulWidget implements PreferredSizeWidget { final OnSelectMenuAction onTap; final BarCallbackActions selectedAction; - const ComunicAppBar( - {Key key, @required this.onTap, @required this.selectedAction}) + const ComunicAppBar({Key key, @required this.onTap, @required this.selectedAction}) : assert(onTap != null), super(key: key); @@ -126,12 +126,17 @@ class ComunicAppBar extends StatefulWidget implements PreferredSizeWidget { } class _ComunicAppBarState extends SafeState { - CountUnreadNotifications _unreadNotifications; + var _unreadNotifications = + CountUnreadNotifications(notifications: 0, conversations: 0); @override void initState() { _refreshCountUnread(); super.initState(); + + // Listen to notifications number update + this.listenChangeState( + (d) => _unreadNotifications.notifications = d.newNum); } void _refreshCountUnread() async { @@ -171,7 +176,7 @@ class _ComunicAppBarState extends SafeState { crossAxisAlignment: CrossAxisAlignment.stretch, children: List.generate( _menuItems.length, - (i) => _MenuItemWidget( + (i) => _MenuItemWidget( item: _menuItems[i], onTap: widget.onTap, isSelected: _menuItems[i].action == widget.selectedAction, @@ -212,9 +217,9 @@ class _MenuItemWidget extends StatelessWidget { color: isSelected ? _secondaryColor() : _primaryColor(), child: !item.isMenu ? InkWell( - child: _buildIconContainer(), - onTap: () => onTap(item.action), - ) + child: _buildIconContainer(), + onTap: () => onTap(item.action), + ) : _buildContextMenuPopupButton(), ), ); @@ -235,15 +240,15 @@ class _MenuItemWidget extends StatelessWidget { newNotice == 0 ? Container() : Material( - color: Colors.red, - child: Padding( - padding: const EdgeInsets.all(2.0), - child: Text(" $newNotice ", - style: TextStyle(color: Colors.white)), - ), - borderRadius: BorderRadius.all( - Radius.circular(50.0), - )), + color: Colors.red, + child: Padding( + padding: const EdgeInsets.all(2.0), + child: Text(" $newNotice ", + style: TextStyle(color: Colors.white)), + ), + borderRadius: BorderRadius.all( + Radius.circular(50.0), + )), Spacer(flex: 2), ], ); @@ -255,9 +260,9 @@ class _MenuItemWidget extends StatelessWidget { child: _buildIconContainer(), itemBuilder: (i) => _menuActionsItem .map((f) => PopupMenuItem( - child: Text(f.label), - value: f.action, - )) + child: Text(f.label), + value: f.action, + )) .toList(), onSelected: onTap, ); diff --git a/lib/ui/widgets/safe_state.dart b/lib/ui/widgets/safe_state.dart index 0ee8316..83f0fee 100644 --- a/lib/ui/widgets/safe_state.dart +++ b/lib/ui/widgets/safe_state.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; /// @author Pierre HUBERT abstract class SafeState extends State { - final _subscriptions = List(); @override @@ -21,8 +20,7 @@ abstract class SafeState extends State { @override void setState(fn) { - if(mounted) - super.setState(fn); + if (mounted) super.setState(fn); } /// Register to a new subscription @@ -30,4 +28,14 @@ abstract class SafeState extends State { void listen(void onEvent(T event)) { _subscriptions.add(EventsHelper.on(onEvent)); } -} \ No newline at end of file + + /// Register to a new subscription + /// + /// Callback will we called inside of setState + @protected + void listenChangeState(void onEvent(T event)) { + _subscriptions.add(EventsHelper.on((d) { + setState(() => onEvent(d)); + })); + } +}