diff --git a/lib/models/message.dart b/lib/models/message.dart index f8dcc232..0ab95a07 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -77,38 +77,51 @@ class ByIndex implements CacheHandler { } Future get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async { - // if in cache, get - if (index < cache.cacheByIndex.length) { + // if in cache, get. But if the cache has unsynced or not in cache, we'll have to do a fetch + if (cache.indexUnsynced == 0 && index < cache.cacheByIndex.length) { return cache.getByIndex(index); } // otherwise we are going to fetch, so we'll fetch a chunk of messages // observationally flutter future builder seemed to be reaching for 20-40 message on pane load, so we start trying to load up to that many messages in one request - var chunk = 40; + var amount = 40; + var start = index; + // we have to keep the indexed cache contiguous so reach back to the end of it and start the fetch from there + if (index > cache.cacheByIndex.length) { + start = cache.cacheByIndex.length; + amount += index - start; + } + + // on android we may have recieved messages on the backend that we didn't process in the UI, get them + // override the index chunk setting, the index math is wrong will we fetch these and these are all that should be missing + if (cache.indexUnsynced > 0) { + start = 0; + amount = cache.indexUnsynced; + } + // check that we aren't asking for messages beyond stored messages - if (index + chunk >= cache.storageMessageCount) { - chunk = cache.storageMessageCount - index; - if (chunk <= 0) { + if (start + amount >= cache.storageMessageCount) { + amount = cache.storageMessageCount - start; + if (amount <= 0) { return Future.value(null); } } - cache.lockIndexes(index, index + chunk); - var msgs = await cwtch.GetMessages(profileOnion, conversationIdentifier, index, chunk); + cache.lockIndexes(start, start + amount); + var msgs = await cwtch.GetMessages(profileOnion, conversationIdentifier, start, amount); int i = 0; // i used to loop through returned messages. if doesn't reach the requested count, we will use it in the finally stanza to error out the remaining asked for messages in the cache try { List messagesWrapper = jsonDecode(msgs); for (; i < messagesWrapper.length; i++) { var messageInfo = messageWrapperToInfo(profileOnion, conversationIdentifier, messagesWrapper[i]); - cache.addIndexed(messageInfo, index + i); + cache.addIndexed(messageInfo, start + i); } - //messageWrapperToInfo } catch (e, stacktrace) { - EnvironmentConfig.debugLog("Error: Getting indexed messages $index to ${index + chunk} failed parsing: " + e.toString() + " " + stacktrace.toString()); + EnvironmentConfig.debugLog("Error: Getting indexed messages $start to ${start + amount} failed parsing: " + e.toString() + " " + stacktrace.toString()); } finally { - if (i != chunk) { - cache.malformIndexes(index + i, index + chunk); + if (i != amount) { + cache.malformIndexes(start + i, start + amount); } } return cache.getByIndex(index); diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index 1ad2ec59..8e67b81a 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -74,6 +74,8 @@ class MessageCache extends ChangeNotifier { // local index to MessageId late List cacheByIndex; + // index unsynced is used on android on reconnect to tell us new messages are in the backend that should be at the front of the index cache + int _indexUnsynced = 0; // map of content hash to MessageId late Map cacheByHash; @@ -87,13 +89,36 @@ class MessageCache extends ChangeNotifier { this._storageMessageCount = storageMessageCount; } - int get indexedLength => cacheByIndex.length; - int get storageMessageCount => _storageMessageCount; set storageMessageCount(int newval) { this._storageMessageCount = newval; } + // On android reconnect, get unread message cound and the last seen Id + // sync this data with what we have cached to determine if/how many messages are now at the front of the index that we don't have cached + void addFrontIndexGap(int count, int lastSeenId) { + // scan across indexed message the unread count amount (that's the last time UI/BE acked a message) + // if we find the last seen ID, the diff of unread count is what's unsynced + for(var i = 0; i < (count+1) && i < cacheByIndex.length; i++) { + if (this.cacheByIndex[i].messageId == lastSeenId) { + // we have + this._indexUnsynced = count - i; + notifyListeners(); + return; + } + } + // we did not find a matching index, diff to the back end is too great, reset index cache + resetIndexCache(); + } + + int get indexUnsynced => _indexUnsynced; + + void resetIndexCache() { + this._indexUnsynced = 0; + cacheByIndex = List.empty(growable: true); + notifyListeners(); + } + MessageInfo? getById(int id) => cache[id]; Future getByIndex(int index) async { @@ -124,6 +149,9 @@ class MessageCache extends ChangeNotifier { void lockIndexes(int start, int end) { for (var i = start; i < end; i++) { this.cacheByIndex.insert(i, LocalIndexMessage(null, isLoading: true)); + if (this._indexUnsynced > 0) { + this._indexUnsynced--; + } } } diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 1848c2f9..4d23ef1e 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -177,6 +177,13 @@ class ProfileInfoState extends ChangeNotifier { profileContact.status = contact["status"]; profileContact.totalMessages = contact["numMessages"]; profileContact.unreadMessages = contact["numUnread"]; + // we only count up to 100 unread messages, if more than that we can't accuratly resync message cache, just reset + if (contact["numUnread"] > 100 || (contact["numUnread"] > 0 && contact["lastSeenMessageId"] == -1)) { + profileContact.messageCache.resetIndexCache(); + } else if (contact["numUnread"] > 0) { + print("contact ${contact["name"]} with unread ${contact["numUnread"]} so addFrontIndexGap"); + profileContact.messageCache.addFrontIndexGap(contact["numUnread"], contact["lastSeenMessageId"]); + } profileContact.lastMessageTime = DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])); } else { this._contacts.add(ContactInfoState( diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 42c504d1..2716da4f 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -128,7 +128,7 @@ class _MessageViewState extends State { backgroundColor: Provider.of(context).theme.backgroundMainColor, floatingActionButton: appState.unreadMessagesBelow ? FloatingActionButton( - child: Icon(Icons.arrow_downward), + child: Icon(Icons.arrow_downward, color: Provider.of(context).current().defaultButtonTextColor), onPressed: () { Provider.of(context, listen: false).initialScrollIndex = 0; Provider.of(context, listen: false).unreadMessagesBelow = false;