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:
parent
c0cd299408
commit
93028c4933
9 changed files with 549 additions and 4 deletions
74
apps/app_seeds/test/ui/saved_searches_screen_test.dart
Normal file
74
apps/app_seeds/test/ui/saved_searches_screen_test.dart
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tane/i18n/strings.g.dart';
|
||||
import 'package:tane/services/saved_searches_store.dart';
|
||||
import 'package:tane/ui/saved_searches_screen.dart';
|
||||
|
||||
import '../support/test_support.dart';
|
||||
|
||||
Widget _wrap(Widget child) {
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
return TranslationProvider(
|
||||
child: MaterialApp(
|
||||
locale: AppLocale.en.flutterLocale,
|
||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SavedSearch _search(String id, {String label = 'Tomatoes'}) =>
|
||||
SavedSearch(id: id, label: label, query: 'tomato', createdAt: 1);
|
||||
|
||||
void main() {
|
||||
testWidgets('shows the empty state when nothing is saved', (tester) async {
|
||||
final store = SavedSearchesStore(InMemorySecretStore());
|
||||
addTearDown(store.close);
|
||||
|
||||
await tester.pumpWidget(_wrap(SavedSearchesScreen(store: store)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.textContaining('No saved searches yet'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('lists a saved search and its new-match badge', (tester) async {
|
||||
final store = SavedSearchesStore(InMemorySecretStore());
|
||||
addTearDown(store.close);
|
||||
await store.save(_search('a'));
|
||||
await store.markMatched('a', 'author:o1');
|
||||
|
||||
await tester.pumpWidget(_wrap(SavedSearchesScreen(store: store)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Tomatoes'), findsOneWidget);
|
||||
expect(find.text('1 new'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('delete confirms then removes the search', (tester) async {
|
||||
final store = SavedSearchesStore(InMemorySecretStore());
|
||||
addTearDown(store.close);
|
||||
await store.save(_search('a'));
|
||||
|
||||
await tester.pumpWidget(_wrap(SavedSearchesScreen(store: store)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(const Key('savedSearches.delete.a')));
|
||||
await tester.pumpAndSettle();
|
||||
// Confirm in the dialog.
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Tomatoes'), findsNothing);
|
||||
expect(await store.exists('a'), isFalse);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue