diff --git a/mobile/lib/features/channels/channel_messages_provider.dart b/mobile/lib/features/channels/channel_messages_provider.dart index 672e21021..aac795c9d 100644 --- a/mobile/lib/features/channels/channel_messages_provider.dart +++ b/mobile/lib/features/channels/channel_messages_provider.dart @@ -62,7 +62,7 @@ class ChannelMessagesNotifier extends Notifier>> { tags: { '#h': [channelId], }, - limit: 50, + limit: 200, ), _handleLiveEvent, ); @@ -87,7 +87,7 @@ class ChannelMessagesNotifier extends Notifier>> { tags: { '#h': [channelId], }, - limit: 50, + limit: 200, ), ); if (!_isCurrentInit(initVersion)) { @@ -106,6 +106,12 @@ class ChannelMessagesNotifier extends Notifier>> { merged.sort((a, b) => a.createdAt.compareTo(b.createdAt)); _lastKnownMessages = merged; state = AsyncData(merged); + + // Auto-prefetch: if deletions/reactions crowded out displayable messages, + // loop fetchOlder() until we have enough content to fill the screen. + // Must clear _initInFlight first so fetchOlder() doesn't short-circuit. + _initInFlight = false; + await _ensureMinDisplayable(initVersion); } catch (e, st) { if (!_isCurrentInit(initVersion)) { return; @@ -146,6 +152,37 @@ class ChannelMessagesNotifier extends Notifier>> { content.contains('member_removed'); } + /// Kinds that are metadata rather than displayable content (deletions, + /// reactions, edits, legacy pre-migration messages). + static const _metadataKinds = {5, 7, 40001, 40003}; + + /// Minimum displayable messages we want after the initial history load. + static const _minDisplayable = 15; + + /// Max extra fetchOlder rounds during auto-prefetch to avoid hammering the + /// relay. + static const _maxPrefetchRounds = 3; + + /// After the initial history fetch, check whether enough user-visible + /// messages were loaded. If deletion/reaction events consumed most of the + /// fetch limit, loop [fetchOlder] to backfill displayable content. + Future _ensureMinDisplayable(int initVersion) async { + for (var i = 0; i < _maxPrefetchRounds; i++) { + if (!_isCurrentInit(initVersion) || _reachedOldest) return; + + final events = state.value; + if (events == null) return; + + final displayable = events + .where((e) => !_metadataKinds.contains(e.kind)) + .length; + if (displayable >= _minDisplayable) return; + + final loaded = await fetchOlder(); + if (!loaded) return; + } + } + /// Merge a new event into the sorted list, deduplicating by ID. static List _mergeEvent( List current, @@ -184,7 +221,7 @@ class ChannelMessagesNotifier extends Notifier>> { tags: { '#h': [channelId], }, - limit: 50, + limit: 100, until: oldest, ), ); diff --git a/mobile/test/features/channels/channel_messages_provider_test.dart b/mobile/test/features/channels/channel_messages_provider_test.dart index 78b81a715..054b3084c 100644 --- a/mobile/test/features/channels/channel_messages_provider_test.dart +++ b/mobile/test/features/channels/channel_messages_provider_test.dart @@ -34,18 +34,20 @@ void main() { .read(channelMessagesProvider(_channelId)) .value!; expect(messages.map((event) => event.id), ['history', 'live']); - expect(relaySession.operations, ['subscribe', 'fetch']); + // The auto-prefetch fires an extra fetchOlder because fewer than 15 + // displayable events were loaded. The deduped result sets _reachedOldest. + expect(relaySession.operations, ['subscribe', 'fetch', 'fetch']); expect( relaySession.liveFilters.single.kinds, EventKind.channelEventKinds, ); expect(relaySession.liveFilters.single.tags['#h'], [_channelId]); - expect(relaySession.liveFilters.single.limit, 50); + expect(relaySession.liveFilters.single.limit, 200); expect( - relaySession.historyFilters.single.kinds, + relaySession.historyFilters.first.kinds, EventKind.channelEventKinds, ); - expect(relaySession.historyFilters.single.tags['#h'], [_channelId]); + expect(relaySession.historyFilters.first.tags['#h'], [_channelId]); }, ); @@ -62,7 +64,7 @@ void main() { final messages = container.read(channelMessagesProvider(_channelId)).value!; expect(messages.map((event) => event.id), ['history']); - expect(relaySession.operations, ['subscribe', 'fetch']); + expect(relaySession.operations, ['subscribe', 'fetch', 'fetch']); }); test(