feat(market): save others' offers as favorites (Wallapop-style)

Adds a Favorites feature: a heart on the offer detail saves another
person's listing to an encrypted, per-identity SavedOffersStore
(keystore JSON snapshot, no plaintext at rest). A new /favorites screen
(wired from the drawer) lists saved offers offline-first and, when a
relay is reachable, flags ones that are gone as "no longer available".
i18n en/es/pt/ast; store + screen + detail-heart tests.
This commit is contained in:
vjrj 2026-07-13 08:21:43 +02:00
parent f6967e8cbb
commit d6225eed38
19 changed files with 806 additions and 3 deletions

View file

@ -17,6 +17,7 @@ import 'services/offer_outbox.dart';
import 'services/onboarding_store.dart';
import 'services/profile_cache.dart';
import 'services/profile_store.dart';
import 'services/saved_offers_store.dart';
import 'services/social_account_store.dart';
import 'services/social_connection.dart';
import 'services/social_service.dart';
@ -28,6 +29,7 @@ import 'ui/auto_backup_gate.dart';
import 'ui/calendar_screen.dart';
import 'ui/chat_list_screen.dart';
import 'ui/chat_screen.dart';
import 'ui/favorites_screen.dart';
import 'ui/home_screen.dart';
import 'ui/intro_screen.dart';
import 'ui/inventory_list_screen.dart';
@ -58,6 +60,7 @@ class TaneApp extends StatelessWidget {
this.messageStore,
this.profileStore,
this.profileCache,
this.savedOffers,
this.socialAccounts,
this.inbox,
this.notifications,
@ -76,6 +79,7 @@ class TaneApp extends StatelessWidget {
messageStore,
profileStore,
profileCache,
savedOffers,
socialAccounts,
inbox,
) {
@ -112,6 +116,9 @@ class TaneApp extends StatelessWidget {
/// Optional cache of peers' published display names.
final ProfileCache? profileCache;
/// Optional store of the user's saved ("favorite") market offers.
final SavedOffersStore? savedOffers;
/// Optional store of the active social identity, for the profile switcher.
final SocialAccountStore? socialAccounts;
@ -140,6 +147,7 @@ class TaneApp extends StatelessWidget {
MessageStore? messageStore,
ProfileStore? profileStore,
ProfileCache? profileCache,
SavedOffersStore? savedOffers,
SocialAccountStore? socialAccounts,
InboxService? inbox,
) {
@ -173,9 +181,18 @@ class TaneApp extends StatelessWidget {
mine: offer.authorPubkeyHex == social.publicKeyHex,
profileCache: profileCache,
selfPubkey: social.publicKeyHex,
savedOffers: savedOffers,
);
},
),
if (social != null && savedOffers != null)
GoRoute(
path: '/favorites',
builder: (context, state) => FavoritesScreen(
savedOffers: savedOffers,
connection: connection,
),
),
if (messageStore != null)
GoRoute(
path: '/messages',