auto-refresh message-list on FB message receive
This commit is contained in:
parent
600f3365f6
commit
59d28d3c49
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/app_bar_filter_dialog.dart';
|
||||
@ -7,9 +6,9 @@ import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_main.dart';
|
||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
|
||||
class SCNAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
SCNAppBar({
|
||||
@ -108,7 +107,7 @@ class _SCNAppBarState extends State<SCNAppBar> {
|
||||
icon: const Icon(FontAwesomeIcons.solidMagnifyingGlass),
|
||||
onPressed: () {
|
||||
value.setShowSearchField(false);
|
||||
AppBarState().notifySearchListeners(_ctrlSearchField.text);
|
||||
AppEvents().notifySearchListeners(_ctrlSearchField.text);
|
||||
_ctrlSearchField.clear();
|
||||
},
|
||||
),
|
||||
@ -157,15 +156,13 @@ class _SCNAppBarState extends State<SCNAppBar> {
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
AppBarState().setShowSearchField(false);
|
||||
AppBarState().notifySearchListeners(_ctrlSearchField.text);
|
||||
AppEvents().notifySearchListeners(_ctrlSearchField.text);
|
||||
_ctrlSearchField.clear();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showFilterDialog(BuildContext context) {
|
||||
double vpWidth = MediaQuery.sizeOf(context).width;
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
|
@ -12,6 +12,7 @@ import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/fb_message.dart';
|
||||
@ -19,6 +20,7 @@ import 'package:simplecloudnotifier/state/globals.dart';
|
||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
import 'package:simplecloudnotifier/utils/notifier.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
@ -308,35 +310,18 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
||||
|
||||
ApplicationLog.info('Received FB message (${foreground ? 'foreground' : 'background'}): ${message.messageId ?? 'NULL'}');
|
||||
|
||||
SCNMessage? receivedMessage;
|
||||
String scn_msg_id;
|
||||
|
||||
try {
|
||||
final scn_msg_id = message.data['scn_msg_id'] as String;
|
||||
final usr_msg_id = message.data['usr_msg_id'] as String;
|
||||
scn_msg_id = message.data['scn_msg_id'] as String;
|
||||
|
||||
final timestamp = DateTime.fromMillisecondsSinceEpoch(int.parse(message.data['timestamp'] as String) * 1000);
|
||||
final priority = int.parse(message.data['priority'] as String);
|
||||
final title = message.data['title'] as String;
|
||||
final channel = message.data['channel'] as String;
|
||||
final channel_id = message.data['channel_id'] as String;
|
||||
final body = message.data['body'] as String;
|
||||
|
||||
Notifier.showLocalNotification(channel_id, channel, 'Channel: ${channel}', title, body, timestamp);
|
||||
|
||||
receivedMessage = SCNMessage(
|
||||
messageID: scn_msg_id,
|
||||
userMessageID: usr_msg_id,
|
||||
timestamp: timestamp.toIso8601String(),
|
||||
priority: priority,
|
||||
trimmed: true,
|
||||
title: title,
|
||||
channelID: channel_id,
|
||||
channelInternalName: channel,
|
||||
content: body,
|
||||
senderIP: '',
|
||||
senderName: '',
|
||||
senderUserID: '',
|
||||
usedKeyID: '',
|
||||
);
|
||||
} catch (exc, trace) {
|
||||
ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace);
|
||||
Notifier.showLocalNotification("@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null);
|
||||
@ -351,8 +336,14 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO add to scn-message-cache
|
||||
//TODO refresh message_list view (if shown/initialized)
|
||||
try {
|
||||
final msg = await APIClient.getMessage(AppAuth(), scn_msg_id);
|
||||
SCNDataCache().addToMessageCache([msg]);
|
||||
if (foreground) AppEvents().notifyMessageReceivedListeners(msg);
|
||||
} catch (exc, trace) {
|
||||
ApplicationLog.error('Failed to query+persist message' + exc.toString(), trace: trace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _receiveLocalDarwinNotification(int id, String? title, String? body, String? payload) {
|
||||
|
@ -9,9 +9,11 @@ import 'package:simplecloudnotifier/pages/message_list/message_filter_chiplet.da
|
||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||
|
||||
class MessageListPage extends StatefulWidget {
|
||||
@ -41,7 +43,8 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
AppBarState().subscribeSearchListener(_onAppBarSearch);
|
||||
AppEvents().subscribeSearchListener(_onAppBarSearch);
|
||||
AppEvents().subscribeMessageReceivedListener(_onMessageReceivedViaNotification);
|
||||
|
||||
_pagingController.addPageRequestListener(_fetchPage);
|
||||
|
||||
@ -68,18 +71,12 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
void _realInitState() {
|
||||
ApplicationLog.debug('MessageListPage::_realInitState');
|
||||
|
||||
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
||||
final msgCache = Hive.box<SCNMessage>('scn-message-cache');
|
||||
|
||||
if (chnCache.isNotEmpty && msgCache.isNotEmpty) {
|
||||
if (SCNDataCache().hasMessagesAndChannels()) {
|
||||
// ==== Use cache values - and refresh in background
|
||||
|
||||
_channels = <String, Channel>{for (var v in chnCache.values) v.channelID: v};
|
||||
_channels = SCNDataCache().getChannelMap();
|
||||
|
||||
final cacheMessages = msgCache.values.toList();
|
||||
cacheMessages.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
_pagingController.value = PagingState(nextPageKey: null, itemList: cacheMessages, error: null);
|
||||
_pagingController.value = PagingState(nextPageKey: null, itemList: SCNDataCache().getMessagesSorted(), error: null);
|
||||
|
||||
_backgroundRefresh(true);
|
||||
} else {
|
||||
@ -99,7 +96,8 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
@override
|
||||
void dispose() {
|
||||
ApplicationLog.debug('MessageListPage::dispose');
|
||||
AppBarState().unsubscribeSearchListener(_onAppBarSearch);
|
||||
AppEvents().unsubscribeSearchListener(_onAppBarSearch);
|
||||
AppEvents().unsubscribeMessageReceivedListener(_onMessageReceivedViaNotification);
|
||||
Navi.modalRouteObserver.unsubscribe(this);
|
||||
_pagingController.dispose();
|
||||
_lifecyleListener.dispose();
|
||||
@ -140,12 +138,12 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
final channels = await APIClient.getChannelList(acc, ChannelSelector.allAny);
|
||||
_channels = <String, Channel>{for (var v in channels) v.channel.channelID: v.channel};
|
||||
|
||||
_setChannelCache(channels); // no await
|
||||
SCNDataCache().setChannelCache(channels); // no await
|
||||
}
|
||||
|
||||
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: cfg.messagePageSize);
|
||||
|
||||
_addToMessageCache(newItems); // no await
|
||||
SCNDataCache().addToMessageCache(newItems); // no await
|
||||
|
||||
ApplicationLog.debug('Finished MessageList::_pagingController::_fetchPage [ ${newItems.length} items and npt: ${thisPageToken} --> ${npt} ]');
|
||||
|
||||
@ -176,12 +174,12 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
setState(() {
|
||||
_channels = <String, Channel>{for (var v in channels) v.channel.channelID: v.channel};
|
||||
});
|
||||
_setChannelCache(channels); // no await
|
||||
SCNDataCache().setChannelCache(channels); // no await
|
||||
}
|
||||
|
||||
final (npt, newItems) = await APIClient.getMessageList(acc, '@start', pageSize: cfg.messagePageSize);
|
||||
|
||||
_addToMessageCache(newItems); // no await
|
||||
SCNDataCache().addToMessageCache(newItems); // no await
|
||||
|
||||
if (fullReplaceState) {
|
||||
// fully replace/reset state
|
||||
@ -278,37 +276,15 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setChannelCache(List<ChannelWithSubscription> channels) async {
|
||||
final cache = Hive.box<Channel>('scn-channel-cache');
|
||||
|
||||
if (cache.length != channels.length) await cache.clear();
|
||||
|
||||
for (var chn in channels) await cache.put(chn.channel.channelID, chn.channel);
|
||||
}
|
||||
|
||||
Future<void> _addToMessageCache(List<SCNMessage> newItems) async {
|
||||
final cfg = AppSettings();
|
||||
|
||||
final cache = Hive.box<SCNMessage>('scn-message-cache');
|
||||
|
||||
for (var msg in newItems) await cache.put(msg.messageID, msg);
|
||||
|
||||
// delete all but the newest 128 messages
|
||||
|
||||
if (cache.length < cfg.messagePageSize) return;
|
||||
|
||||
final allValues = cache.values.toList();
|
||||
|
||||
allValues.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
for (var val in allValues.sublist(cfg.messagePageSize)) {
|
||||
await cache.delete(val.messageID);
|
||||
}
|
||||
}
|
||||
|
||||
void _onAppBarSearch(String str) {
|
||||
setState(() {
|
||||
_filterChiplets = _filterChiplets.where((element) => false).toList() + [MessageFilterChiplet(label: str, value: str, type: MessageFilterChipletType.search)];
|
||||
});
|
||||
}
|
||||
|
||||
void _onMessageReceivedViaNotification(SCNMessage msg) {
|
||||
setState(() {
|
||||
_pagingController.itemList = [msg] + (_pagingController.itemList ?? []);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ class AppBarState extends ChangeNotifier {
|
||||
|
||||
AppBarState._internal() {}
|
||||
|
||||
List<void Function(String)> _searchListeners = [];
|
||||
|
||||
bool _loadingIndeterminate = false;
|
||||
bool get loadingIndeterminate => _loadingIndeterminate;
|
||||
|
||||
@ -28,18 +26,4 @@ class AppBarState extends ChangeNotifier {
|
||||
_showSearchField = v;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void subscribeSearchListener(void Function(String) listener) {
|
||||
_searchListeners.add(listener);
|
||||
}
|
||||
|
||||
void unsubscribeSearchListener(void Function(String) listener) {
|
||||
_searchListeners.remove(listener);
|
||||
}
|
||||
|
||||
void notifySearchListeners(String query) {
|
||||
for (var listener in _searchListeners) {
|
||||
listener(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
47
flutter/lib/state/app_events.dart
Normal file
47
flutter/lib/state/app_events.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
|
||||
class AppEvents {
|
||||
static AppEvents? _singleton = AppEvents._internal();
|
||||
|
||||
factory AppEvents() {
|
||||
return _singleton ?? (_singleton = AppEvents._internal());
|
||||
}
|
||||
|
||||
AppEvents._internal() {}
|
||||
|
||||
List<void Function(String)> _searchListeners = [];
|
||||
List<void Function(SCNMessage)> _messageReceivedListeners = [];
|
||||
|
||||
void subscribeSearchListener(void Function(String) listener) {
|
||||
_searchListeners.add(listener);
|
||||
}
|
||||
|
||||
void unsubscribeSearchListener(void Function(String) listener) {
|
||||
_searchListeners.remove(listener);
|
||||
}
|
||||
|
||||
void notifySearchListeners(String query) {
|
||||
ApplicationLog.debug('[AppEvents] onSearch: $query');
|
||||
|
||||
for (var listener in _searchListeners) {
|
||||
listener(query);
|
||||
}
|
||||
}
|
||||
|
||||
void subscribeMessageReceivedListener(void Function(SCNMessage) listener) {
|
||||
_messageReceivedListeners.add(listener);
|
||||
}
|
||||
|
||||
void unsubscribeMessageReceivedListener(void Function(SCNMessage) listener) {
|
||||
_messageReceivedListeners.remove(listener);
|
||||
}
|
||||
|
||||
void notifyMessageReceivedListeners(SCNMessage msg) {
|
||||
ApplicationLog.debug('[AppEvents] onMessageReceived: ${msg.messageID}');
|
||||
|
||||
for (var listener in _messageReceivedListeners) {
|
||||
listener(msg);
|
||||
}
|
||||
}
|
||||
}
|
60
flutter/lib/state/scn_data_cache.dart
Normal file
60
flutter/lib/state/scn_data_cache.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:simplecloudnotifier/models/channel.dart';
|
||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
||||
|
||||
class SCNDataCache {
|
||||
SCNDataCache._internal();
|
||||
static final SCNDataCache _instance = SCNDataCache._internal();
|
||||
factory SCNDataCache() => _instance;
|
||||
|
||||
Future<void> addToMessageCache(List<SCNMessage> newItems) async {
|
||||
final cfg = AppSettings();
|
||||
|
||||
final cache = Hive.box<SCNMessage>('scn-message-cache');
|
||||
|
||||
for (var msg in newItems) await cache.put(msg.messageID, msg);
|
||||
|
||||
// delete all but the newest 128 messages
|
||||
|
||||
if (cache.length < cfg.messagePageSize) return;
|
||||
|
||||
final allValues = cache.values.toList();
|
||||
|
||||
allValues.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
for (var val in allValues.sublist(cfg.messagePageSize)) {
|
||||
await cache.delete(val.messageID);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setChannelCache(List<ChannelWithSubscription> channels) async {
|
||||
final cache = Hive.box<Channel>('scn-channel-cache');
|
||||
|
||||
if (cache.length != channels.length) await cache.clear();
|
||||
|
||||
for (var chn in channels) await cache.put(chn.channel.channelID, chn.channel);
|
||||
}
|
||||
|
||||
bool hasMessagesAndChannels() {
|
||||
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
||||
final msgCache = Hive.box<SCNMessage>('scn-message-cache');
|
||||
|
||||
return chnCache.isNotEmpty && msgCache.isNotEmpty;
|
||||
}
|
||||
|
||||
Map<String, Channel> getChannelMap() {
|
||||
final chnCache = Hive.box<Channel>('scn-channel-cache');
|
||||
|
||||
return <String, Channel>{for (var v in chnCache.values) v.channelID: v};
|
||||
}
|
||||
|
||||
List<SCNMessage> getMessagesSorted() {
|
||||
final msgCache = Hive.box<SCNMessage>('scn-message-cache');
|
||||
|
||||
final cacheMessages = msgCache.values.toList();
|
||||
cacheMessages.sort((a, b) => -1 * a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
return cacheMessages;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user