feat(web): tane.comunes.org landing + localized app screenshots
- Hugo site (site/): EN/ES intro + legal (generated from docs/legal), an
ES/EN About page from docs/{que-es-tane,what-is-tane}.md, seed-green theme,
responsive header, store-badge placeholders, multi-stage Hugo->nginx image.
- Golden screenshot harness (test/screenshots/): en,es,fr,de,pt,ja + RTL demo,
real fonts via DejaVu Sans + FontManifest; skip-by-default tag so CI stays
green. collect_screenshots.sh feeds site/ and fastlane phoneScreenshots.
- Drop editorial notes from the public explainer.
This commit is contained in:
parent
bbf2e97027
commit
8dce6f7027
117 changed files with 1958 additions and 4 deletions
161
apps/app_seeds/test/screenshots/screenshots_test.dart
Normal file
161
apps/app_seeds/test/screenshots/screenshots_test.dart
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
@Tags(['screenshots'])
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tane/data/species_repository.dart';
|
||||
import 'package:tane/data/variety_repository.dart';
|
||||
import 'package:tane/db/database.dart';
|
||||
import 'package:tane/i18n/strings.g.dart';
|
||||
import 'package:tane/state/offers_cubit.dart';
|
||||
import 'package:tane/state/variety_detail_cubit.dart';
|
||||
import 'package:tane/ui/calendar_screen.dart';
|
||||
import 'package:tane/ui/home_screen.dart';
|
||||
import 'package:tane/ui/inventory_list_screen.dart';
|
||||
import 'package:tane/ui/market_screen.dart';
|
||||
import 'package:tane/ui/variety_detail_screen.dart';
|
||||
|
||||
import '../support/test_support.dart';
|
||||
import 'screenshot_support.dart';
|
||||
|
||||
/// Generates localized marketing/store screenshots as golden PNGs.
|
||||
///
|
||||
/// Run (writes the PNGs) — the `screenshots` tag is skipped by default
|
||||
/// (dart_test.yaml), so `--run-skipped` is required:
|
||||
/// flutter test --update-goldens --run-skipped --tags screenshots test/screenshots/screenshots_test.dart
|
||||
///
|
||||
/// Output: test/screenshots/goldens/`<locale>`/`<screen>`.png for en/es/fr/de/pt/ja,
|
||||
/// plus goldens/rtl/ — a right-to-left *layout* demo (English strings mirrored;
|
||||
/// the app ships no Arabic translation yet, so this proves the design is RTL-safe
|
||||
/// rather than claiming an Arabic locale). These are raw device-canvas frames
|
||||
/// with no marketing chrome; add captions/device frames downstream if a store
|
||||
/// requires them. `tool/collect_screenshots.sh` copies them into site/ and fastlane/.
|
||||
void main() {
|
||||
setUpAll(() async {
|
||||
await loadScreenshotFonts();
|
||||
// Touch mode never draws focus rings, so no highlight frames the FAB/fields.
|
||||
FocusManager.instance.highlightStrategy =
|
||||
FocusHighlightStrategy.alwaysTouch;
|
||||
});
|
||||
|
||||
late AppDatabase db;
|
||||
late VarietyRepository repo;
|
||||
late SpeciesRepository species;
|
||||
late SeededInventory seeded;
|
||||
|
||||
setUp(() async {
|
||||
db = newTestDatabase();
|
||||
repo = newTestRepository(db);
|
||||
species = newTestSpeciesRepository(db);
|
||||
seeded = await seedShowcase(db, repo);
|
||||
});
|
||||
tearDown(() => db.close());
|
||||
|
||||
Future<void> capture(WidgetTester tester, String dir, String name) async {
|
||||
tester.view.physicalSize = screenshotSize;
|
||||
tester.view.devicePixelRatio = screenshotPixelRatio;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(
|
||||
find.byType(MaterialApp),
|
||||
matchesGoldenFile('goldens/$dir/$name.png'),
|
||||
);
|
||||
await disposeTree(tester);
|
||||
}
|
||||
|
||||
// A POPULATED market: a seeded OffersCubit (no transport) feeding MarketBody,
|
||||
// so the offer list renders with no live discovery stream to hang
|
||||
// pumpAndSettle. (Live discovery is exercised by offers_cubit_test.dart; see
|
||||
// the note in market_screen_test.dart on why we don't drive it via widgets.)
|
||||
Future<Widget> market() async => Builder(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t.market.title),
|
||||
actions: [IconButton(icon: const Icon(Icons.tune), onPressed: () {})],
|
||||
),
|
||||
body: BlocProvider<OffersCubit>(
|
||||
create: (_) => _SeededOffersCubit(_sampleOffers()),
|
||||
child: MarketBody(onConfigure: () {}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Each entry renders one screen; the runner re-seeds and re-locales per shot.
|
||||
final screens = <String, Future<Widget> Function()>{
|
||||
'home': () async => const HomeScreen(marketEnabled: true),
|
||||
'inventory': () async => const InventoryListScreen(),
|
||||
'market': market,
|
||||
'calendar': () async => const CalendarScreen(initialMonth: 4),
|
||||
'detail': () async => BlocProvider(
|
||||
create: (_) => VarietyDetailCubit(repo, seeded.heroVarietyId),
|
||||
child: const VarietyDetailScreen(),
|
||||
),
|
||||
};
|
||||
|
||||
for (final locale in screenshotLocales) {
|
||||
for (final entry in screens.entries) {
|
||||
testWidgets('${locale.languageCode}/${entry.key}', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
screenshotApp(
|
||||
repository: repo,
|
||||
species: species,
|
||||
locale: locale,
|
||||
child: await entry.value(),
|
||||
),
|
||||
);
|
||||
await capture(tester, locale.languageCode, entry.key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// RTL layout demo: base (English) strings, forced right-to-left, so the
|
||||
// mirrored inventory proves the design is RTL-safe. Not a translated locale.
|
||||
testWidgets('rtl/inventory', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
screenshotApp(
|
||||
repository: repo,
|
||||
species: species,
|
||||
locale: AppLocale.en,
|
||||
textDirection: TextDirection.rtl,
|
||||
child: const InventoryListScreen(),
|
||||
),
|
||||
);
|
||||
await capture(tester, 'rtl', 'inventory');
|
||||
});
|
||||
}
|
||||
|
||||
/// An [OffersCubit] pre-loaded with a fixed offer list. It carries a no-op
|
||||
/// transport (never called — offers are pre-seeded) purely so `isOnline` is
|
||||
/// true and MarketBody renders the list instead of the "can't reach" state.
|
||||
class _SeededOffersCubit extends OffersCubit {
|
||||
_SeededOffersCubit(List<Offer> offers) : super(_NoopOfferTransport()) {
|
||||
emit(state.copyWith(offers: offers, hasSearched: true, areaGeohash: 'sp3e'));
|
||||
}
|
||||
}
|
||||
|
||||
/// A transport that is present (so the market reads as online) but never used.
|
||||
class _NoopOfferTransport implements OfferTransport {
|
||||
@override
|
||||
Stream<Offer> discover(DiscoveryQuery query) => const Stream.empty();
|
||||
@override
|
||||
Future<PublishResult> publish(Offer offer) => throw UnimplementedError();
|
||||
@override
|
||||
Future<void> retract(String offerId) async {}
|
||||
@override
|
||||
Future<void> close() async {}
|
||||
}
|
||||
|
||||
/// A small, internationally-neutral set of nearby offers across gift / swap /
|
||||
/// sale and colour-coded botanical families.
|
||||
List<Offer> _sampleOffers() => [
|
||||
Offer(id: 'o1', authorPubkeyHex: 'a1' * 32, summary: 'Cherry tomato', type: OfferType.gift, category: 'Solanaceae', approxGeohash: 'sp3e9', isOrganic: true),
|
||||
Offer(id: 'o2', authorPubkeyHex: 'a2' * 32, summary: 'Climbing bean', type: OfferType.exchange, category: 'Fabaceae', approxGeohash: 'sp3e8'),
|
||||
Offer(id: 'o3', authorPubkeyHex: 'a3' * 32, summary: 'Sunflower', type: OfferType.sale, category: 'Asteraceae', approxGeohash: 'sp3ec', priceAmount: 2, priceCurrency: '€'),
|
||||
Offer(id: 'o4', authorPubkeyHex: 'a4' * 32, summary: 'Sweet basil', type: OfferType.gift, category: 'Lamiaceae', approxGeohash: 'sp3e2', isOrganic: true),
|
||||
Offer(id: 'o5', authorPubkeyHex: 'a5' * 32, summary: 'Purple carrot', type: OfferType.exchange, category: 'Apiaceae', approxGeohash: 'sp3ef'),
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue