From cb41a5ff447e88ae8bd721f0b6a3928c6611cc0b Mon Sep 17 00:00:00 2001 From: LeoLox <58687994+leo-lox@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:26:34 +0100 Subject: [PATCH] main feed new notes --- lib/config/default_relays.dart | 7 +++ .../repositories/note_repository_impl.dart | 32 +++++++++++ .../repositories/note_repository.dart | 9 ++++ lib/domain_layer/usecases/main_feed.dart | 15 +++++- .../providers/main_feed_provider.dart | 36 ++++++++++++- .../providers/ndk_provider.dart | 2 + .../nostr_page/user_feed_original_view.dart | 53 +++++++------------ 7 files changed, 118 insertions(+), 36 deletions(-) create mode 100644 lib/config/default_relays.dart diff --git a/lib/config/default_relays.dart b/lib/config/default_relays.dart new file mode 100644 index 00000000..4dcb058e --- /dev/null +++ b/lib/config/default_relays.dart @@ -0,0 +1,7 @@ +List CAMELUS_BOOTSTRAP_RELAYS = [ + "wss://strfry.iris.to", + "wss://relay.damus.io", + "wss://relay.nostr.band", + "wss://relay.snort.social", + "wss://nos.lol", +]; diff --git a/lib/data_layer/repositories/note_repository_impl.dart b/lib/data_layer/repositories/note_repository_impl.dart index 5055dc4e..6f7363ee 100644 --- a/lib/data_layer/repositories/note_repository_impl.dart +++ b/lib/data_layer/repositories/note_repository_impl.dart @@ -62,6 +62,7 @@ class NoteRepositoryImpl implements NoteRepository { ); } + /// Get all notes by a list of authors using a query @override Stream getTextNotesByAuthors({ required List authors, @@ -91,4 +92,35 @@ class NoteRepositoryImpl implements NoteRepository { (event) => NostrNoteModel.fromNDKEvent(event), ); } + + /// Get all notes by a list of authors using a subscription + @override + Stream subscribeTextNotesByAuthors({ + required List authors, + required String requestId, + int? since, + int? until, + int? limit, + List? eTags, + }) { + ndk.Filter filter = ndk.Filter( + authors: authors, + kinds: [ndk_entities.Nip01Event.TEXT_NODE_KIND], + since: since, + until: until, + limit: limit, + eTags: eTags, + ); + + final response = dartNdkSource.dartNdk.requests.subscription( + filters: [filter], + name: requestId, + cacheRead: true, + cacheWrite: true, + ); + + return response.stream.map( + (event) => NostrNoteModel.fromNDKEvent(event), + ); + } } diff --git a/lib/domain_layer/repositories/note_repository.dart b/lib/domain_layer/repositories/note_repository.dart index 70dd3110..7836d5b1 100644 --- a/lib/domain_layer/repositories/note_repository.dart +++ b/lib/domain_layer/repositories/note_repository.dart @@ -13,4 +13,13 @@ abstract class NoteRepository { int? limit, List? eTags, }); + + Stream subscribeTextNotesByAuthors({ + required List authors, + required String requestId, + int? since, + int? until, + int? limit, + List? eTags, + }); } diff --git a/lib/domain_layer/usecases/main_feed.dart b/lib/domain_layer/usecases/main_feed.dart index 2eefb7e4..cfbac2c4 100644 --- a/lib/domain_layer/usecases/main_feed.dart +++ b/lib/domain_layer/usecases/main_feed.dart @@ -41,11 +41,17 @@ class MainFeed { } final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; - final newNotesStream = _noteRepository.getTextNotesByAuthors( + final newNotesStream = _noteRepository.subscribeTextNotesByAuthors( authors: contactList.contacts, requestId: userFeedFreshId, since: now, ); + + final filterRootNotes = newNotesStream.where((event) => event.isRoot); + + filterRootNotes.listen((event) { + _newNotesController.add(event); + }); } /// load later timelineevents then @@ -93,7 +99,12 @@ class MainFeed { filterRootNotes.listen((event) { _controller.add(event); }); + } - //_controller.addStream(filterRootNotes); + /// integrate new notes into main feed + void integrateNotes(List events) { + for (final event in events) { + _controller.add(event); + } } } diff --git a/lib/presentation_layer/providers/main_feed_provider.dart b/lib/presentation_layer/providers/main_feed_provider.dart index 3561e867..dc0ff4eb 100644 --- a/lib/presentation_layer/providers/main_feed_provider.dart +++ b/lib/presentation_layer/providers/main_feed_provider.dart @@ -34,7 +34,13 @@ final getMainFeedProvider = Provider((ref) { final mainFeedStateProvider = NotifierProvider.family, String>( - MainFeedState.new); + MainFeedState.new, +); + +final mainFeedNewNotesStateProvider = + NotifierProvider.family, String>( + MainFeedNewNotesState.new, +); // todo: add stateProvider for new events class MainFeedState extends FamilyNotifier, String> { @@ -83,3 +89,31 @@ class MainFeedState extends FamilyNotifier, String> { state = []; } } + +class MainFeedNewNotesState extends FamilyNotifier, String> { + @override + List build(String arg) { + start(arg); + return []; + } + + void _addEvents(List events) { + state = [...state, ...events] + ..sort((a, b) => b.created_at.compareTo(a.created_at)); + } + + start(String pubkey) { + final mainFeedProvider = ref.read(getMainFeedProvider); + final eventStreamBuffer = mainFeedProvider.newNotesStream + .bufferTime(const Duration( + milliseconds: 500, + )) + .where((events) => events.isNotEmpty); + + eventStreamBuffer.listen((events) { + _addEvents(events); + }); + + mainFeedProvider.subscribeToFreshNotes(npub: pubkey); + } +} diff --git a/lib/presentation_layer/providers/ndk_provider.dart b/lib/presentation_layer/providers/ndk_provider.dart index 1ce5cc0a..b59279b9 100644 --- a/lib/presentation_layer/providers/ndk_provider.dart +++ b/lib/presentation_layer/providers/ndk_provider.dart @@ -1,6 +1,7 @@ import 'package:ndk/ndk.dart'; import 'package:riverpod/riverpod.dart'; +import '../../config/default_relays.dart'; import 'db_object_box_provider.dart'; import 'event_signer_provider.dart'; import 'event_verifier.dart'; @@ -18,6 +19,7 @@ final ndkProvider = Provider((ref) { cache: dbObjectBox, eventSigner: eventSigner, eventVerifier: eventVerifier, + bootstrapRelays: CAMELUS_BOOTSTRAP_RELAYS, ); final ndk = Ndk(ndkConfig); diff --git a/lib/presentation_layer/routes/nostr/nostr_page/user_feed_original_view.dart b/lib/presentation_layer/routes/nostr/nostr_page/user_feed_original_view.dart index d0fd8dc5..0567bae6 100644 --- a/lib/presentation_layer/routes/nostr/nostr_page/user_feed_original_view.dart +++ b/lib/presentation_layer/routes/nostr/nostr_page/user_feed_original_view.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:developer'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:camelus/presentation_layer/atoms/new_posts_available.dart'; import 'package:camelus/presentation_layer/atoms/refresh_indicator_no_need.dart'; @@ -33,8 +32,6 @@ class _UserFeedOriginalViewState extends ConsumerState { final Completer _servicesReady = Completer(); - bool _newPostsAvailable = false; - // new ######### // final List timelineEvents = []; // Removed this line late final Stream> _eventStreamBuffer; @@ -75,29 +72,6 @@ class _UserFeedOriginalViewState extends ConsumerState { widget.scrollControllerFeed.addListener(_scrollListener); } - void _setupNewNotesListener() { - return; - - final mainFeedProvider = ref.read(getMainFeedProvider); - - mainFeedProvider.newNotesStream - .bufferTime(const Duration( - seconds: 5, - )) - .where((events) => events.isNotEmpty) - .listen((events) { - log("new notes stream event"); - if (mounted) { - setState(() { - _newPostsAvailable = true; - }); - } - - // notify navigation bar - ref.read(navigationBarProvider).newNotesCount = events.length; - }); - } - void _setupNavBarHomeListener() { var provider = ref.read(navigationBarProvider); _subscriptions.add(provider.onTabHome.listen((event) { @@ -106,7 +80,10 @@ class _UserFeedOriginalViewState extends ConsumerState { } void _handleHomeBarTab() { - if (_newPostsAvailable) { + final newNotesLenth = + ref.watch(mainFeedNewNotesStateProvider(widget.pubkey)).length; + print("new notes length: $newNotesLenth"); + if (newNotesLenth > 0) { _integrateNewNotes(); return; } @@ -125,9 +102,15 @@ class _UserFeedOriginalViewState extends ConsumerState { duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); - setState(() { - _newPostsAvailable = false; - }); + + final newNotesP = ref.watch(mainFeedNewNotesStateProvider(widget.pubkey)); + + final notesToIntegrate = newNotesP; + ref.watch(getMainFeedProvider).integrateNotes(notesToIntegrate); + + // delte new notes in FeedNew + newNotesP.clear(); + ref.watch(navigationBarProvider).resetNewNotesCount(); } @@ -135,7 +118,6 @@ class _UserFeedOriginalViewState extends ConsumerState { if (!mounted) return; _setupScrollListener(); - _setupNewNotesListener(); _setupNavBarHomeListener(); @@ -167,6 +149,11 @@ class _UserFeedOriginalViewState extends ConsumerState { @override Widget build(BuildContext context) { final timelineEvents = ref.watch(mainFeedStateProvider(widget.pubkey)); + final newNotesEvents = + ref.watch(mainFeedNewNotesStateProvider(widget.pubkey)); + + ref.watch(navigationBarProvider).newNotesCount = newNotesEvents.length; + return FutureBuilder( future: _servicesReady.future, builder: (context, snapshot) { @@ -205,11 +192,11 @@ class _UserFeedOriginalViewState extends ConsumerState { }, ), ), - if (_newPostsAvailable) + if (newNotesEvents.isNotEmpty) Container( margin: const EdgeInsets.only(top: 20), child: newPostsAvailable( - name: "new posts", + name: "${newNotesEvents.length} new posts", onPressed: () { _integrateNewNotes(); }),