feat(market): host and publish the cover photo with each offer

Wire the Blossom MediaTransport into publishing: shareable lots carry
their varietyId, publishLots uploads the lot's cover photo and puts the
returned URL on the offer (best-effort — a hosting failure still publishes
the offer, just without a photo). Add a configurable media server (Comunes
default, empty to opt out) to settings and the market advanced config.
This commit is contained in:
vjrj 2026-07-10 21:12:36 +02:00
parent 5bc1715637
commit fa53295439
17 changed files with 280 additions and 22 deletions

View file

@ -58,7 +58,11 @@ class _MarketScreenState extends State<MarketScreen> {
final repo =
widget.outbox != null ? context.read<VarietyRepository>() : null;
setState(() => _loading = true);
final cubit = await createOffersCubit(widget.social, widget.settings);
final cubit = await createOffersCubit(
widget.social,
widget.settings,
repository: repo,
);
final area = await widget.settings.areaGeohash();
if (!mounted) {
await cubit.close();
@ -599,6 +603,7 @@ class _ConfigSheet extends StatefulWidget {
class _ConfigSheetState extends State<_ConfigSheet> {
final _area = TextEditingController();
final _servers = TextEditingController();
final _mediaServer = TextEditingController();
bool _loading = true;
bool _locationBusy = false;
String? _locationError;
@ -613,6 +618,7 @@ class _ConfigSheetState extends State<_ConfigSheet> {
Future<void> _load() async {
_area.text = await widget.settings.areaGeohash();
_servers.text = (await widget.settings.relayUrls()).join('\n');
_mediaServer.text = await widget.settings.mediaServerUrl();
_precision = await widget.settings.searchPrecision();
if (mounted) setState(() => _loading = false);
}
@ -620,6 +626,7 @@ class _ConfigSheetState extends State<_ConfigSheet> {
Future<void> _save() async {
await widget.settings.setAreaGeohash(_area.text);
await widget.settings.setRelayUrls(_servers.text.split('\n'));
await widget.settings.setMediaServerUrl(_mediaServer.text);
await widget.settings.setSearchPrecision(_precision);
if (mounted) Navigator.of(context).pop(true);
}
@ -649,6 +656,7 @@ class _ConfigSheetState extends State<_ConfigSheet> {
void dispose() {
_area.dispose();
_servers.dispose();
_mediaServer.dispose();
super.dispose();
}
@ -788,6 +796,16 @@ class _ConfigSheetState extends State<_ConfigSheet> {
helperMaxLines: 2,
),
),
const SizedBox(height: 14),
TextField(
key: const Key('market.mediaServer'),
controller: _mediaServer,
decoration: InputDecoration(
labelText: t.market.mediaServerLabel,
helperText: t.market.mediaServerHelp,
helperMaxLines: 2,
),
),
],
),
),