feat(market): saved searches with in-app alerts (Wallapop-style)

Save the current market search (query + facets) and get notified when a
matching offer appears in your zone while the app is open, with a catch-up
scan on start. Adds a save affordance in the search bar, a saved-searches
list (apply/delete) with a per-search new-match badge, an AppBar entry
badged with the unseen total, and notification-tap routing to the list.
Alerts evaluate against the user's current area, dedup via seen keys, and
skip own/blocked/hidden/non-active offers. en+es strings.
This commit is contained in:
vjrj 2026-07-17 13:10:52 +02:00
parent c0cd299408
commit 93028c4933
9 changed files with 549 additions and 4 deletions

View file

@ -18,6 +18,7 @@ import 'services/onboarding_store.dart';
import 'services/profile_cache.dart';
import 'services/profile_store.dart';
import 'services/saved_offers_store.dart';
import 'services/saved_searches_store.dart';
import 'services/social_account_store.dart';
import 'services/social_connection.dart';
import 'services/social_service.dart';
@ -36,6 +37,7 @@ import 'ui/inventory_list_screen.dart';
import 'ui/legal_screen.dart';
import 'ui/plantares_screen.dart';
import 'ui/sales_screen.dart';
import 'ui/saved_searches_screen.dart';
import 'ui/market_offer_detail_screen.dart';
import 'ui/market_screen.dart';
import 'ui/offline_banner.dart';
@ -62,6 +64,7 @@ class TaneApp extends StatelessWidget {
this.profileStore,
this.profileCache,
this.savedOffers,
this.savedSearches,
this.socialAccounts,
this.inbox,
this.notifications,
@ -81,6 +84,7 @@ class TaneApp extends StatelessWidget {
profileStore,
profileCache,
savedOffers,
savedSearches,
socialAccounts,
inbox,
) {
@ -88,6 +92,8 @@ class TaneApp extends StatelessWidget {
// the router only exists now; taps only happen while the app is foreground,
// so a live router reference is enough.
notifications?.onTapChat = (pubkey) => _router.push('/chat/$pubkey');
// A tapped saved-search alert opens the saved-searches list.
notifications?.onTapSearch = (_) => _router.push('/saved-searches');
}
final VarietyRepository repository;
@ -120,6 +126,9 @@ class TaneApp extends StatelessWidget {
/// Optional store of the user's saved ("favorite") market offers.
final SavedOffersStore? savedOffers;
/// Optional store of the user's saved market searches (with alerts).
final SavedSearchesStore? savedSearches;
/// Optional store of the active social identity, for the profile switcher.
final SocialAccountStore? socialAccounts;
@ -149,6 +158,7 @@ class TaneApp extends StatelessWidget {
ProfileStore? profileStore,
ProfileCache? profileCache,
SavedOffersStore? savedOffers,
SavedSearchesStore? savedSearches,
SocialAccountStore? socialAccounts,
InboxService? inbox,
) {
@ -170,6 +180,7 @@ class TaneApp extends StatelessWidget {
location: location,
outbox: outbox,
onboarding: onboarding,
savedSearches: savedSearches,
),
),
if (social != null && connection != null)
@ -196,6 +207,12 @@ class TaneApp extends StatelessWidget {
connection: connection,
),
),
if (social != null && savedSearches != null)
GoRoute(
path: '/saved-searches',
builder: (context, state) =>
SavedSearchesScreen(store: savedSearches),
),
if (messageStore != null)
GoRoute(
path: '/messages',