feat(market): search + pull-to-refresh in the market list
Match the inventory patterns the market was missing: - Search: OffersState gains a `query` + `visibleOffers` getter and OffersCubit.search() filters the already-discovered offers locally by summary (case-insensitive), never re-hitting the transport. The filter survives a refresh. Rounded search field mirrors inventory's. - Pull-to-refresh: RefreshIndicator re-runs discovery for the current area (empty state included, so a swipe re-scans). Text filter kept. - i18n: market.searchHint / market.noMatches (en/es/pt). Note: the shared slang output (strings*.g.dart, incl. the generated Asturian locale) is regenerated wholesale, so it also carries the in-progress `ast` locale keys already present in the working tree.
This commit is contained in:
parent
bd18978aff
commit
beb29915d8
11 changed files with 1614 additions and 21 deletions
|
|
@ -240,26 +240,95 @@ class MarketBody extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
final cubit = context.read<OffersCubit>();
|
||||
// Pull-to-refresh re-runs discovery for the current area (keeping the
|
||||
// text filter), so the list is never stuck on a stale scan.
|
||||
Future<void> refresh() => cubit.discover(state.areaGeohash);
|
||||
|
||||
// Nothing discovered at all: keep the friendly empty state, but make it
|
||||
// pull-to-refreshable so a swipe re-scans.
|
||||
if (state.offers.isEmpty) {
|
||||
return _EmptyState(
|
||||
icon: Icons.grass_outlined,
|
||||
title: t.market.empty,
|
||||
body: '',
|
||||
return RefreshIndicator(
|
||||
onRefresh: refresh,
|
||||
child: ListView(
|
||||
// AlwaysScrollable so the pull gesture works with a single child.
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
child: _EmptyState(
|
||||
icon: Icons.grass_outlined,
|
||||
title: t.market.empty,
|
||||
body: '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: state.offers.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) {
|
||||
final o = state.offers[i];
|
||||
final mine = o.authorPubkeyHex == selfPubkey;
|
||||
return _OfferCard(
|
||||
offer: o,
|
||||
mine: mine,
|
||||
onContact: mine ? null : () => context.push('/chat/${o.authorPubkeyHex}'),
|
||||
);
|
||||
},
|
||||
|
||||
final visible = state.visibleOffers;
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 4),
|
||||
child: TextField(
|
||||
key: const Key('market.search'),
|
||||
decoration: InputDecoration(
|
||||
hintText: t.market.searchHint,
|
||||
prefixIcon: const Icon(Icons.search, color: seedMuted),
|
||||
hintStyle: const TextStyle(color: seedMuted),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
onChanged: cubit.search,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: refresh,
|
||||
child: visible.isEmpty
|
||||
// Discovered offers exist, but the search filtered them out.
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: _EmptyState(
|
||||
icon: Icons.search_off,
|
||||
title: t.market.noMatches,
|
||||
body: '',
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: visible.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) {
|
||||
final o = visible[i];
|
||||
final mine = o.authorPubkeyHex == selfPubkey;
|
||||
return _OfferCard(
|
||||
offer: o,
|
||||
mine: mine,
|
||||
onContact: mine
|
||||
? null
|
||||
: () =>
|
||||
context.push('/chat/${o.authorPubkeyHex}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue