glanzlicht / MeinWeimar_APP

Official MeinWeimar App

Verified secure showcase
204 Files 2 MB Public size 14 Redacted Dart · PHP · XML · GITIGNORE · YAML · PLIST

Project overview

MeinWeimar_APP is a verified code showcase with 204 visible project entries across 2 top-level modules. The primary detected language is Dart; protected files remain visible in the structure while their contents stay private.

README

MeinWeimar — Flutter App & WordPress Plugin

> Working in Progress · Private portfolio repository · Active development

MeinWeimar is a location-based local platform for residents, students and visitors. This repository intentionally contains only two product parts:

  1. the Flutter consumer app;
  2. the meinweimar-core WordPress plugin that provides the editorial/admin
  3. layer and the REST contract consumed by the app.

The complete public website, WordPress core, databases, private infrastructure and unrelated platform modules are not part of this repository.

MeinWeimar consumer app preview

Why this project is interesting

MeinWeimar combines a mobile/web Flutter client with WordPress used as a structured content and administration backend. The goal is a maintainable product boundary: content, branding, navigation, categories, media and configuration are managed in WordPress and delivered through a versioned REST contract instead of being hardcoded in the Flutter UI.

The project demonstrates work across:

  • Flutter/Dart application architecture for iOS, Android and Web;
  • WordPress plugin architecture with custom post types, taxonomies, admin
  • settings and REST endpoints;

  • localized German/English UI and backend-controlled labels;
  • discovery/list/detail flows for places, services, events, classifieds, jobs
  • and real estate;

  • map, GPS, geocoding, marker and navigation contracts;
  • caching, ETag/config-version handling and offline-safe fallbacks;
  • role-aware account, claim, favorites, moderation and messaging boundaries;
  • automated Flutter tests, PHP/REST contract checks and release builds.

Repository structure

flutter-app/       Flutter consumer application
wordpress-plugin/  meinweimar-core WordPress plugin
screenshots/       Curated portfolio preview only

Current status

This is an active Working in Progress project. The core architecture and many end-to-end flows are implemented and continuously tested. Native device polish, production content, final legal/privacy review and further product modules are still being developed. The repository does not claim to be a finished production release.

Local development

Flutter app

cd flutter-app
flutter pub get
flutter analyze
flutter test
flutter run --dart-define=MW_API_BASE_URL=https://example.test/wp-json/meinweimar/v1

The API base URL and other environment-specific values are supplied at build or run time. No credentials are stored in the repository.

WordPress plugin

Copy wordpress-plugin into a WordPress installation's wp-content/plugins/meinweimar-core directory and activate it in the admin area. The plugin owns the MeinWeimar content types, editorial settings, permissions, REST responses, media/configuration contracts and frontend integration points.

Privacy and repository boundary

This repository is private and is prepared for controlled technical review. Secrets, production database exports, user data, WordPress uploads, private keys and infrastructure configuration must remain outside version control. The public-facing portfolio page uses a separate, redacted presentation layer and does not expose the private repository directly.

For technical reviewers

The most relevant entry points are:

  • flutter-app/lib/core — shared configuration, networking, localization,
  • theme and location contracts;

  • flutter-app/lib/features — consumer-facing product surfaces;
  • flutter-app/lib/repositories — API boundary and data access;
  • wordpress-plugin/includes — plugin services, REST, admin and contracts;
  • wordpress-plugin/assets — admin/frontend integration assets.

MeinWeimar is deliberately presented as an evolving product rather than a finished demo: the repository shows the architectural decisions, the working implementation and the remaining engineering work transparently.

flutter-app/test/shared/app_shell_test.dartDart
import 'package:consumer_app/core/config/app_config.dart';
import 'package:consumer_app/core/location/location_controller.dart';
import 'package:consumer_app/core/navigation/canonical_module_routes.dart';
import 'package:consumer_app/core/networking/api_client.dart';
import 'package:consumer_app/l10n/app_localizations.dart';
import 'package:consumer_app/l10n/locale_controller.dart';
import 'package:consumer_app/models/account_models.dart';
import 'package:consumer_app/models/claim_models.dart';
import 'package:consumer_app/models/content_models.dart';
import 'package:consumer_app/models/image_suggestion_models.dart';
import 'package:consumer_app/models/notification_models.dart';
import 'package:consumer_app/repositories/account_repository.dart';
import 'package:consumer_app/repositories/claim_repository.dart';
import 'package:consumer_app/repositories/discovery_repository.dart';
import 'package:consumer_app/repositories/geo_repository.dart';
import 'package:consumer_app/repositories/image_suggestion_repository.dart';
import 'package:consumer_app/repositories/notifications_repository.dart';
import 'package:consumer_app/shared/app_shell/app_shell.dart';
import 'package:consumer_app/features/discovery/resource_browser_page.dart';
import 'package:consumer_app/features/more/more_page.dart';
import 'package:consumer_app/shared/navigation/app_navigation.dart';
import 'package:consumer_app/features/claim/claim_page.dart';
import 'package:consumer_app/features/saved/saved_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('startup location explanation does not block the shell', (
tester,
) async {
final locationController = LocationController(
storage: _PromptLocationStorage(),
);
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
locationController: locationController,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Standort für lokale Entdeckungen'), findsOneWidget);
expect(find.byType(AlertDialog), findsNothing);
expect(find.text('Orte'), findsAtLeastNWidgets(1));
});
testWidgets('offline bootstrap exposes a localized retry action', (
tester,
) async {
var retries = 0;
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
onRetryBootstrap: () => retries++,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Du bist offline'), findsOneWidget);
expect(find.text('Erneut versuchen'), findsOneWidget);
final retryButton = tester.widget<TextButton>(
find.widgetWithText(TextButton, 'Erneut versuchen'),
);
retryButton.onPressed!();
expect(retries, 1);
});
testWidgets('global shell navigation exposes logo menu and module grid', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
Scaffold(
body: const SizedBox.expand(),
bottomNavigationBar: AppNavigationBar(
items: config.navigation,
selectedIndex: 0,
onSelected: (_) {},
onOpenMenu: () {},
brand: config.brand,
labelBuilder: (item) => switch (item.labelKey) {
'navStart' => 'Start',
'navMap' => 'Karte',
'navEvents' => 'Events',
'navSaved' => 'Gespeichert',
_ => item.labelKey,
},
),
),
),
);
await tester.pumpAndSettle();
expect(find.byType(AppNavigationBar), findsOneWidget);
expect(find.text('Start'), findsOneWidget);
expect(find.text('Gespeichert'), findsOneWidget);
expect(
tester.getSize(find.byType(AppNavigationBar)).height,
lessThanOrEqualTo(100),
);
});
testWidgets('configured navigation collapses duplicate canonical routes', (
tester,
) async {
final defaults = NavigationItem.defaults
.map(
(item) => <String, dynamic>{
'id': item.id,
'route': item.route,
'label_key': item.labelKey,
'icon': item.icon,
'enabled': true,
},
)
.toList(growable: false);
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': defaults,
'app_navigation': {
'bottom': [
{
'id': 'places-alias',
'route': '/places/',
'label_key': 'navPlaces',
'icon': 'place',
'enabled': true,
},
{
'id': 'places',
'route': '/places/',
'label_key': 'navPlaces',
'icon': 'place',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
],
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
expect(navigation.items.map((item) => item.id), ['places-alias', 'events']);
navigation.onSelected(0);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
});
testWidgets(
'fallback shell keeps all canonical primary module destinations',
(tester) async {
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
for (final label in [
'Orte',
'Events',
'Karte',
'Kleinanzeigen',
'Jobs',
'Immobilien',
]) {
expect(find.text(label), findsAtLeastNWidgets(1));
}
},
);
testWidgets(
'places route keeps schema config separate from API taxonomy categories',
(tester) async {
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'content_categories': {
'places': {
'taxonomy': 'mw_place_category',
'profiles': {
'gastronomy': {
'slugs': ['restaurants'],
'labels': {'restaurants': 'Restaurants'},
'fields': [],
'required': [],
},
},
},
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
navigation.onSelected(0);
await tester.pumpAndSettle();
expect(find.text('Kunst & Kultur'), findsOneWidget);
expect(find.text('Restaurants'), findsNothing);
},
);
testWidgets('global map queries every canonical geo module', (tester) async {
final repository = _FakeDiscoveryRepository();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: repository,
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
final navigation = tester.widget<AppNavigationBar>(
find.byType(AppNavigationBar),
);
navigation.onSelected(2); // canonical fallback order: places, events, map
await tester.pumpAndSettle();
expect(
repository.typeCalls.any(
(types) =>
types.length == CanonicalModuleRoutes.ids.length &&
types.every(CanonicalModuleRoutes.ids.contains),
),
isTrue,
);
});
testWidgets('fullscreen menu renders configured secondary modules', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
FullscreenModuleMenuPage(
items: config.navigation,
brand: config.brand,
labelBuilder: (item) => switch (item.labelKey) {
'navCommunity' => 'Community',
'moduleServices' => 'Services und Einrichtungen',
'moduleClassifieds' => 'Kleinanzeigen',
'moduleJobs' => 'Jobs',
'moduleRealEstate' => 'Immobilien',
'moduleNews' => 'News',
'moduleGuides' => 'Guides',
_ => item.labelKey,
},
),
),
);
await tester.pumpAndSettle();
expect(find.text('Services und Einrichtungen'), findsOneWidget);
expect(find.text('Kleinanzeigen'), findsOneWidget);
expect(find.text('Jobs'), findsOneWidget);
expect(find.byIcon(Icons.close), findsOneWidget);
});
testWidgets('opens a canonical module for an incoming deep link', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
initialPath: config.navigation
.firstWhere((item) => item.id == 'real-estate')
.route,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Immobilien'), findsOneWidget);
});
testWidgets('all public module deep links open the same localized shell', (
tester,
) async {
const expectedTitles = <String, String>{
'places': 'Orte',
'services': 'Services und Einrichtungen',
'events': 'Events',
'classifieds': 'Kleinanzeigen',
'jobs': 'Jobs',
'real-estate': 'Immobilien',
};
for (final entry in expectedTitles.entries) {
await tester.pumpWidget(
_app(
AppShell(
key: ValueKey(entry.key),
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
initialPath: CanonicalModuleRoutes.pathFor(entry.key),
),
),
);
await tester.pumpAndSettle();
expect(find.text(entry.value), findsOneWidget);
}
});
testWidgets(
'home tile and fullscreen menu use the same canonical module shell',
(tester) async {
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.drag(find.byType(ListView).first, const Offset(-360, 0));
await tester.pumpAndSettle();
// The home quick-access entry is now a circular, card-free item. The
// first Jobs label is therefore the home entry after the horizontal
// slider has been moved into view.
await tester.tap(find.text('Jobs').first);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
await tester.tap(find.byType(BackButton));
await tester.pumpAndSettle();
await tester.tapAt(tester.getCenter(find.byType(AppNavigationBar)));
await tester.pumpAndSettle();
expect(find.byType(FullscreenModuleMenuPage), findsOneWidget);
final menuJobs = find.text('Jobs').last;
await tester.ensureVisible(menuJobs);
await tester.pumpAndSettle();
await tester.tap(menuJobs);
await tester.pumpAndSettle();
expect(find.byType(ResourceBrowserPage), findsOneWidget);
},
);
testWidgets('switching canonical modules refreshes the resource type', (
tester,
) async {
final repository = _FakeDiscoveryRepository();
await tester.pumpWidget(
_app(
AppShell(
snapshot: BootstrapSnapshot.fallback(),
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: repository,
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
repository.typeCalls = [];
final navigationFinder = find.byType(AppNavigationBar);
final navigation = tester.widget<AppNavigationBar>(navigationFinder);
final eventsIndex = navigation.items.indexWhere(
(item) => item.id == 'events',
);
expect(eventsIndex, greaterThanOrEqualTo(0));
navigation.onSelected(eventsIndex);
await tester.pumpAndSettle();
expect(
repository.typeCalls.any(
(types) => types.length == 1 && types.first == 'events',
),
isTrue,
);
});
testWidgets(
'configured fullscreen menu keeps every public content route reachable',
(tester) async {
final sparseNavigation = <Map<String, dynamic>>[
{
'id': 'start',
'route': '/',
'label_key': 'navStart',
'icon': 'explore',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
{
'id': 'more',
'route': '/mehr/',
'label_key': 'navMore',
'icon': 'menu',
'enabled': true,
},
];
final allRoutes = NavigationItem.defaults
.map(
(item) => <String, dynamic>{
'id': item.id,
'route': item.route,
'label_key': item.labelKey,
'icon': item.icon,
'enabled': true,
},
)
.toList(growable: false);
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': allRoutes,
'app_navigation': {
'bottom': sparseNavigation,
'fullscreen': sparseNavigation,
},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.tapAt(tester.getCenter(find.byType(AppNavigationBar)));
await tester.pumpAndSettle();
expect(find.byType(FullscreenModuleMenuPage), findsOneWidget);
for (final label in [
'Orte',
'Services und Einrichtungen',
'Events',
'Kleinanzeigen',
'Jobs',
'Immobilien',
]) {
expect(find.text(label), findsOneWidget);
}
expect(find.text('Mehr'), findsNothing);
},
);
testWidgets('configured More navigation renders its body inside the shell', (
tester,
) async {
final bottom = <Map<String, dynamic>>[
{
'id': 'start',
'route': '/',
'label_key': 'navStart',
'icon': 'explore',
'enabled': true,
},
{
'id': 'map',
'route': '/karte/',
'label_key': 'navMap',
'icon': 'map',
'enabled': true,
},
{
'id': 'events',
'route': '/events/',
'label_key': 'navEvents',
'icon': 'event',
'enabled': true,
},
{
'id': 'community',
'route': '/community/',
'label_key': 'navCommunity',
'icon': 'people',
'enabled': true,
},
{
'id': 'saved',
'route': '/gespeichert/',
'label_key': 'navSaved',
'icon': 'bookmark',
'enabled': true,
},
{
'id': 'more',
'route': '/mehr/',
'label_key': 'navMore',
'icon': 'menu',
'enabled': true,
},
];
final snapshot = BootstrapSnapshot.fromJson({
'config': {
'app_navigation_configured': true,
'navigation': bottom,
'app_navigation': {'bottom': bottom, 'fullscreen': bottom},
},
}, baseUrl: Uri.parse('https://example.test'));
await tester.pumpWidget(
_app(
AppShell(
snapshot: snapshot,
localeController: LocaleController(store: MemoryLocaleStore()),
apiClient: ApiClient(
baseUrl: Uri.parse('https://example.test'),
locale: () => 'de-DE',
),
repository: _FakeDiscoveryRepository(),
accountRepository: _FakeAccountRepository(),
claimRepository: _FakeClaimRepository(),
geoRepository: _FakeGeoRepository(),
imageSuggestionRepository: _FakeImageSuggestionRepository(),
notificationsRepository: _FakeNotificationsRepository(),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.menu).last);
await tester.pumpAndSettle();
expect(find.byType(MorePage), findsOneWidget);
expect(find.text('Mehr'), findsAtLeastNWidgets(2));
expect(find.text('Mein Profil'), findsOneWidget);
});
testWidgets('saved surface renders its responsive intro and empty state', (
tester,
) async {
final config = AppConfig.fallback();
await tester.pumpWidget(
_app(
SavedPage(
accountRepository: _FakeAccountRepository(),
discoveryRepository: _FakeDiscoveryRepository(),
geoRepository: _FakeGeoRepository(),
geoConfig: config.geo,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Gespeichert'), findsOneWidget);
expect(
find.text(
'Deine gespeicherten Orte und Events bleiben hier griffbereit.',
),
findsOneWidget,
);
expect(find.text('Noch nichts gespeichert'), findsOneWidget);
});
testWidgets('claim surface renders localized status and object metadata', (
tester,
) async {
await tester.pumpWidget(
_app(
ClaimPage(
repository: _FakeClaimRepository(
response: ClaimListResponse(
claims: [
ClaimRecord(
id: 7,
objectType: 'classifieds',
objectId: 42,
claimantUserId: 8,
organizationName: 'MeinWeimar Handel',
claimantNote: 'Bitte prüfen',
status: 'approved',
decisionNote: 'Freigabe erteilt',
createdAt: '2026-09-26T10:00:00Z',
updatedAt: '2026-09-26T10:00:00Z',
),
],
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Business-Claims'), findsNWidgets(2));
expect(find.text('1 Anfrage'), findsOneWidget);
await tester.drag(find.byType(CustomScrollView), const Offset(0, -400));
await tester.pumpAndSettle();
expect(find.text('Freigegeben'), findsOneWidget);
expect(find.text('Kleinanzeige #42'), findsOneWidget);
expect(find.text('Deine Notiz'), findsOneWidget);
expect(find.text('Freigabe erteilt'), findsOneWidget);
});
}
Widget _app(Widget child) {
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: const Locale('de'),
home: child,
);
}
class _PromptLocationStorage implements LocationStorage {
LocationPersistedState state = const LocationPersistedState();
@override
Future<LocationPersistedState> read() async => state;
@override
Future<void> write(LocationPersistedState next) async => state = next;
}
class _FakeDiscoveryRepository implements DiscoveryRepository {
static final _empty = DiscoveryResponse.fromJson({
'items': const [],
'meta': {
'categories': [
{
'slug': 'kunst-kultur',
'name': 'Kunst & Kultur',
'parent_slug': '',
'count': 1,
'taxonomy': 'mw_place_category',
},
{
'slug': 'museum-2',
'name': 'Museum',
'parent_slug': 'kunst-kultur',
'count': 1,
'taxonomy': 'mw_place_category',
},
],
},
});
List<List<String>> typeCalls = const [];
@override
Future<DiscoveryResponse> discover({
required List<String> types,
String? search,
double? latitude,
double? longitude,
double? radiusKm = 10,
int page = 1,
int perPage = 20,
String? category,
String? region,
String? status,
Map<String, String> filters = const {},
String sort = 'auto',
double? minLatitude,
double? maxLatitude,
double? minLongitude,
double? maxLongitude,
}) async {
typeCalls = [...typeCalls, List<String>.from(types)];
return _empty;
}
@override
Future<DiscoveryItem> detail({required String resource, required int id}) =>
throw UnimplementedError();
}
class _FakeAccountRepository implements AccountRepository {
@override
Future<ProfileData> me() async => ProfileData.fromJson(const {});
@override
Future<ProfileData> updateMe(Map<String, dynamic> fields) => me();
@override
Future<ProfileData> publicProfile(int userId) => throw UnimplementedError();
@override
Future<SavedResponse> favorites() async => const SavedResponse(items: []);
@override
Future<bool> updateFavorite({
required String objectType,
required int objectId,
required bool saved,
}) async => saved;
}
class _FakeClaimRepository implements ClaimRepository {
_FakeClaimRepository({this.response = const ClaimListResponse(claims: [])});
final ClaimListResponse response;
@override
Future<ClaimListResponse> list() async => response;
}
class _FakeGeoRepository implements GeoRepository {
@override
Future<List<GeoSearchResult>> searchAddress(String query, {int limit = 5}) =>
throw UnimplementedError();
}
class _FakeImageSuggestionRepository implements ImageSuggestionRepository {
@override
Future<ImageSuggestionData> submit({
required String targetType,
required int targetId,
required List<int> bytes,
required String filename,
required bool rightsHolderConfirmed,
required bool lawfulSourceConfirmed,
required bool personsConsentConfirmed,
required bool platformLicenseConfirmed,
required String idempotencyKey,
String source = '',
String license = '',
String photographer = '',
String description = '',
}) => throw UnimplementedError();
@override
Future<ImageSuggestionList> own({String? status}) =>
Future.value(ImageSuggestionList.fromJson(const {}));
@override
Future<ImageSuggestionData> respond({
required int suggestionId,
required String response,
required String idempotencyKey,
}) => throw UnimplementedError();
}
class _FakeNotificationsRepository implements NotificationsRepository {
@override
Future<NotificationList> list({String? status}) =>
Future.value(NotificationList.fromJson(const {}));
@override
Future<void> markRead(int id) => throw UnimplementedError();
}

Architecture

High-level modules detected from the verified public snapshot. Protected implementation details are intentionally omitted.

flutter-appmodule
wordpress-pluginmodule

Technology stack

DartPHPJavaScriptARBLOCKPBXPROJFlutter / Dart
Dart56.1%
PHP29.4%
JavaScript4.0%
ARB2.9%
LOCK1.7%
PBXPROJ1.7%
CSS1.5%
Markdown0.4%