glanzlicht / Meinweimar_Tourguide

Intelligenter Tourguide für Weimar

Verified secure showcase
167 Files 833 KB Public size 12 Redacted Dart · Markdown · YAML · HTML · JSON · GITIGNORE

Project overview

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

README

MeinWeimar Tourguide

MeinWeimar Tourguide

Eine produktionsnahe Flutter-App fuer Stadtentdeckung, Tourplanung und Sprachlernen in Weimar. Die App verbindet eine responsive mobile und webfaehige Oberflaeche mit einer WordPress-REST-API und macht aus redaktionellen Inhalten konkrete Wege, Orte und Empfehlungen.

Dieses Repository zeigt neben der UI auch die technische Produktarbeit dahinter: API-Integration, Offline-tolerante Datenhaltung, Lokalisierung, Routing, Audio/TTS, Analytics, Deep Links und automatisierte Qualitaetssicherung.

App-Eindruecke

Start · Entdecken · Touren · Karte · Mehr

Produktumfang

  • Entdecken: POIs, Touren, Veranstaltungen, Partner und Themen mit Suche,
  • Filtern und fehlertoleranter Trefferlogik.

  • Unterwegs: Kartenansicht, Standort, Fussgaenger-Routing, Tourfortschritt,
  • Favoriten und eine persoenliche Route aus der eigenen Weimar-Liste.

  • Mehrsprachigkeit: UI und CMS-Inhalte in Deutsch, Englisch,
  • Niederlaendisch, Franzoesisch, Polnisch, Italienisch, Spanisch und Tschechisch.

  • Audio und Lernen: Guide-Audio, Flutter TTS sowie SprachGuide-Kurse und
  • Lern-Touren.

  • Redaktion und Betrieb: WordPress REST API, Oeffnungszeiten mit
  • Zeitzonenlogik, Kampagnen, Partnerplatzierungen, Deep Links und First-Party- Analytics.

  • Responsive UX: kleine Displays, grosse Systemschrift, dynamische
  • Kartenhoehen und versionierte Web-Releases ohne veraltete Service-Worker- Assets.

Technischer Steckbrief

BereichUmsetzung
FrameworkFlutter / Dart 3.4+
PlattformeniOS, Android und Web
DatenWordPress REST API, typisierte Models und Repositories
Kartenflutter_map, latlong2, Standort und Fussgaenger-Routing
Audiojust_audio, flutter_tts
Lokaler ZustandShared Preferences, begrenztes Caching und Analytics-Puffer
QualitaetFlutter Analyzer, Widget- und Service-Tests, Lokalisierungs- und API-Modelltests

Die Codebasis ist nach Verantwortlichkeiten gegliedert:

lib/
	core/          gemeinsame API-, Lokalisierungs- und Navigationslogik
	data/          Datenzugriff und DTO-nahe Strukturen
	features/      produktbezogene Screens und Flows
	models/        fachliche Modelle
	repositories/  Datenzugriff hinter stabilen Schnittstellen
	services/      Routing, Audio, Analytics und Plattformdienste
	widgets/       wiederverwendbare UI-Bausteine
test/            fokussierte Tests fuer Services, Modelle und Widgets

Lokal starten

Voraussetzungen: aktuelles Flutter SDK und das darin enthaltene Dart SDK.

flutter pub get
flutter analyze
flutter test
flutter run -d chrome

Die Anwendung verwendet standardmaessig die oeffentliche API: https://tour.meinweimar.de/wp-json/meinweimar/v1

Web-Release

Fuer einen Web-Release das versionierte Build-Skript verwenden:

./tool/build_web_release.sh

Das Skript legt den Runtime-Build unter build/web/releases/<release-id>/ ab. Launcher und Versionsendpunkt bleiben bewusst nicht cachebar, damit Browser nach einem Reload die aktuelle Version auflosen. Startup-Diagnostik ist nur mit ?mwdebug=1 aktiv.

Dokumentation

  • [Deep Links](DEEP_LINKS.md) - Web-Routen fuer Bereiche, Touren und POIs
  • [Projektstatus](PROJECT_STATE.md) - aktueller Funktions- und Integrationsstand
  • [Changelog](CHANGELOG.md) - nachvollziehbare Produkt- und UX-Iterationen
  • [API-Integration](docs/API_INTEGRATION.md) - technische Integrationsnotizen
lib/features/events/event_ui_text.dartDart
import 'package:flutter/material.dart';
import '../../models/event.dart';
class EventUiText {
const EventUiText(this.lang);
final String lang;
String get event => _v({
'de': 'Veranstaltung',
'en': 'Event',
'nl': 'Evenement',
'fr': 'Événement',
'pl': 'Wydarzenie',
'it': 'Evento',
'es': 'Evento',
'cs': 'Událost'
});
String get eventsInWeimar => _v({
'de': 'Veranstaltungen in Weimar',
'en': 'Events in Weimar',
'nl': 'Evenementen in Weimar',
'fr': 'Événements à Weimar',
'pl': 'Wydarzenia w Weimarze',
'it': 'Eventi a Weimar',
'es': 'Eventos en Weimar',
'cs': 'Události ve Výmaru'
});
String get noEvents => _v({
'de': 'Aktuell sind noch keine kommenden Veranstaltungen eingetragen.',
'en': 'There are currently no upcoming events.',
'nl': 'Er zijn momenteel geen komende evenementen.',
'fr': 'Il n’y a actuellement aucun événement à venir.',
cych wydarzeń.',
'it': 'Al momento non ci sono eventi in programma.',
'es': 'Actualmente no hay próximos eventos.',
'cs': 'Momentálně nejsou naplánovány žádné nadcházející události.'
});
String get todayInWeimar => _v({
'de': 'Heute in Weimar',
'en': 'Today in Weimar',
'nl': 'Vandaag in Weimar',
'fr': 'Aujourd’hui à Weimar',
'pl': 'Dziś w Weimarze',
'it': 'Oggi a Weimar',
'es': 'Hoy en Weimar',
'cs': 'Dnes ve Výmaru'
});
String get comingUp => _v({
'de': 'Demnächst in Weimar',
'en': 'Coming up in Weimar',
'nl': 'Binnenkort in Weimar',
'fr': 'Prochainement à Weimar',
'pl': 'Wkrótce w Weimarze',
'it': 'Prossimamente a Weimar',
'es': 'Próximamente en Weimar',
'cs': 'Brzy ve Výmaru'
});
String get eventsToday => _v({
'de': 'Veranstaltungen, die heute stattfinden.',
'en': 'Events happening today.',
'nl': 'Evenementen die vandaag plaatsvinden.',
'fr': 'Événements ayant lieu aujourd’hui.',
ce się dzisiaj.',
'it': 'Eventi in programma oggi.',
'es': 'Eventos que tienen lugar hoy.',
'cs': 'Události, které se konají dnes.'
});
String get upcomingEvents => _v({
'de': 'Die nächsten Veranstaltungen.',
'en': 'Upcoming events.',
'nl': 'De volgende evenementen.',
'fr': 'Les prochains événements.',
'pl': 'Najbliższe wydarzenia.',
'it': 'I prossimi eventi.',
'es': 'Los próximos eventos.',
'cs': 'Nadcházející události.'
});
String get all => _v({
'de': 'Alle',
'en': 'All',
'nl': 'Alle',
'fr': 'Tous',
'pl': 'Wszystkie',
'it': 'Tutti',
'es': 'Todos',
'cs': 'Vše'
});
String get today => _v({
'de': 'HEUTE',
'en': 'TODAY',
'nl': 'VANDAAG',
'fr': 'AUJOURD’HUI',
'pl': 'DZISIAJ',
'it': 'OGGI',
'es': 'HOY',
'cs': 'DNES'
});
String get allDay => _v({
'de': 'GANZTÄGIG',
'en': 'ALL DAY',
'nl': 'HELE DAG',
'fr': 'TOUTE LA JOURNÉE',
'pl': 'CAŁY DZIEŃ',
'it': 'TUTTO IL GIORNO',
'es': 'TODO EL DÍA',
'cs': 'CELÝ DEN'
});
String get allDayLower => _v({
'de': 'ganztägig',
'en': 'all day',
'nl': 'hele dag',
'fr': 'toute la journée',
'pl': 'cały dzień',
'it': 'tutto il giorno',
'es': 'todo el día',
'cs': 'celý den'
});
String get date => _v({
'de': 'Termin',
'en': 'Date',
'nl': 'Datum',
'fr': 'Date',
'pl': 'Termin',
'it': 'Data',
'es': 'Fecha',
'cs': 'Termín'
});
String get location => _v({
'de': 'Ort',
'en': 'Location',
'nl': 'Locatie',
'fr': 'Lieu',
'pl': 'Miejsce',
'it': 'Luogo',
'es': 'Lugar',
'cs': 'Místo'
});
String get admission => _v({
'de': 'Eintritt',
'en': 'Admission',
'nl': 'Toegang',
'fr': 'Entrée',
'pl': 'Wstęp',
'it': 'Ingresso',
'es': 'Entrada',
'cs': 'Vstupné'
});
String get organizer => _v({
'de': 'Veranstalter',
'en': 'Organizer',
'nl': 'Organisator',
'fr': 'Organisateur',
'pl': 'Organizator',
'it': 'Organizzatore',
'es': 'Organizador',
'cs': 'Pořadatel'
});
String get booking => _v({
'de': 'Anmeldung',
'en': 'Booking',
'nl': 'Reservering',
'fr': 'Réservation',
'pl': 'Rezerwacja',
'it': 'Prenotazione',
'es': 'Reserva',
'cs': 'Rezervace'
});
String get bookingRequired => _v({
'de': 'Buchung / Anmeldung erforderlich',
'en': 'Booking / registration required',
'nl': 'Reserveren / aanmelden verplicht',
'fr': 'Réservation / inscription requise',
'pl': 'Wymagana rezerwacja / rejestracja',
'it': 'Prenotazione / registrazione richiesta',
'es': 'Reserva / inscripción obligatoria',
'cs': 'Nutná rezervace / registrace'
});
String get accessibility => _v({
'de': 'Barrierefreiheit',
'en': 'Accessibility',
'nl': 'Toegankelijkheid',
'fr': 'Accessibilité',
'pl': 'Dostępność',
'it': 'Accessibilità',
'es': 'Accesibilidad',
'cs': 'Bezbariérovost'
});
String get accessible => _v({
'de': 'Als rollstuhlgerecht / barrierearm markiert',
'en': 'Marked wheelchair accessible',
'nl': 'Gemarkeerd als rolstoeltoegankelijk',
'fr': 'Indiqué comme accessible en fauteuil roulant',
'pl': 'Oznaczone jako dostępne dla osób na wózkach',
'it': 'Indicato come accessibile in sedia a rotelle',
'es': 'Marcado como accesible en silla de ruedas',
'cs': 'Označeno jako bezbariérové'
});
String get about => _v({
'de': 'Über die Veranstaltung',
'en': 'About the event',
'nl': 'Over het evenement',
'fr': 'À propos de l’événement',
'pl': 'O wydarzeniu',
'it': 'Informazioni sull’evento',
'es': 'Sobre el evento',
'cs': 'O události'
});
String get freeAdmission => _v({
'de': 'Eintritt frei',
'en': 'Free admission',
'nl': 'Gratis toegang',
'fr': 'Entrée gratuite',
'pl': 'Wstęp wolny',
'it': 'Ingresso gratuito',
'es': 'Entrada gratuita',
'cs': 'Vstup zdarma'
});
String get freeDetails => _v({
'de': 'Kostenlos / Infos',
'en': 'Free / details',
'nl': 'Gratis / info',
'fr': 'Gratuit / infos',
'pl': 'Bezpłatnie / informacje',
'it': 'Gratis / info',
'es': 'Gratis / información',
'cs': 'Zdarma / informace'
});
String get tickets => _v({
'de': 'Tickets / Buchung',
'en': 'Tickets / booking',
'nl': 'Tickets / reserveren',
'fr': 'Billets / réservation',
'pl': 'Bilety / rezerwacja',
'it': 'Biglietti / prenotazione',
'es': 'Entradas / reserva',
'cs': 'Vstupenky / rezervace'
});
String get joinOnline => _v({
'de': 'Online teilnehmen',
'en': 'Join online',
'nl': 'Online deelnemen',
'fr': 'Participer en ligne',
cz online',
'it': 'Partecipa online',
'es': 'Participar en línea',
'cs': 'Připojit se online'
});
String get organizerWebsite => _v({
'de': 'Website',
'en': 'Website',
'nl': 'Website',
'fr': 'Site web',
'pl': 'Strona internetowa',
'it': 'Sito web',
'es': 'Sitio web',
'cs': 'Web'
});
String get eventWebsite => _v({
'de': 'Website der Veranstaltung',
'en': 'Event website',
'nl': 'Website van het evenement',
'fr': 'Site de l’événement',
'pl': 'Strona wydarzenia',
'it': 'Sito dell’evento',
'es': 'Web del evento',
'cs': 'Web události'
});
String get routeThere => _v({
'de': 'Route hierher',
'en': 'Directions',
'nl': 'Route hierheen',
'fr': 'Itinéraire',
'pl': 'Trasa tutaj',
'it': 'Indicazioni',
'es': 'Cómo llegar',
'cs': 'Trasa sem'
});
String get originalPage => _v({
'de': 'Originalseite öffnen',
'en': 'Open original page',
'nl': 'Originele pagina openen',
'fr': 'Ouvrir la page d’origine',
',
'it': 'Apri la pagina originale',
'es': 'Abrir página original',
'cs': 'Otevřít původní stránku'
});
String get postponed => _v({
'de': 'Diese Veranstaltung wurde verschoben.',
'en': 'This event has been postponed.',
'nl': 'Dit evenement is uitgesteld.',
'fr': 'Cet événement a été reporté.',
'pl': 'To wydarzenie zostało przełożone.',
'it': 'Questo evento è stato rinviato.',
'es': 'Este evento ha sido aplazado.',
'cs': 'Tato událost byla odložena.'
});
String get soldOut => _v({
'de': 'Diese Veranstaltung ist ausverkauft.',
'en': 'This event is sold out.',
'nl': 'Dit evenement is uitverkocht.',
'fr': 'Cet événement est complet.',
'pl': 'To wydarzenie jest wyprzedane.',
'it': 'Questo evento è esaurito.',
'es': 'Este evento está agotado.',
'cs': 'Tato událost je vyprodaná.'
});
String _v(Map<String, String> values) => values[lang] ?? values['en']!;
}
bool eventSameDay(DateTime a, DateTime b) =>
a.year == b.year && a.month == b.month && a.day == b.day;
String eventDateRangeLabel(BuildContext context, Event event) {
final l = MaterialLocalizations.of(context);
final start = DateTime(event.start.year, event.start.month, event.start.day);
final end = DateTime(event.end.year, event.end.month, event.end.day);
if (eventSameDay(start, end)) return l.formatMediumDate(start);
return '${l.formatMediumDate(start)} – ${l.formatMediumDate(end)}';
}
String eventTimeRangeLabel(BuildContext context, Event event) {
if (event.allDay) return '';
final start = TimeOfDay.fromDateTime(event.start).format(context);
final end = TimeOfDay.fromDateTime(event.end).format(context);
return start == end ? start : '$start – $end';
}
String eventHomeMetaLabel(BuildContext context, Event event,
{required bool isToday}) {
final lang = Localizations.localeOf(context).languageCode;
final t = EventUiText(lang);
final date = eventDateRangeLabel(context, event).toUpperCase();
if (event.allDay) {
return isToday ? '${t.today} · ${t.allDay}' : '$date · ${t.allDay}';
}
final time = eventTimeRangeLabel(context, event);
return isToday ? '${t.today} · $time' : '$date · $time';
}

Architecture

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

libmodule
testmodule
docsmodule
webmodule
toolmodule

Technology stack

DartMarkdownLOCKHTMLShellHTACCESSFlutter / Dart
Dart89.9%
Markdown3.9%
LOCK3.1%
HTML1.7%
Shell0.8%
HTACCESS0.2%
YAML0.1%
METADATA0.1%