import 'package:flutter/material.dart';
import '../../core/localization/app_localizations.dart';
import '../../core/theme/app_colors.dart';
import '../../data/app_data_scope.dart';
import '../../data/location_scope.dart';
import '../../data/location_store.dart';
import '../../models/ad_campaign.dart';
import '../../widgets/section_header.dart';
import '../../widgets/sponsored_banner.dart';
import '../../widgets/tour_card.dart';
import '../../widgets/tourguide_brand_header.dart';
import '../../widgets/home_now_section.dart';
import '../../widgets/home_events_section.dart';
import '../../widgets/first_run_onboarding.dart';
import '../map/map_page.dart';
import '../learning/learning_home_page.dart';
import '../learning/new_in_weimar_page.dart';
import '../planner/route_planner_page.dart';
import '../tours/tour_detail_page.dart';
import '../tours/tours_page.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
void _openTours(BuildContext context,
{String? category,
String? topicSlug,
bool familyOnly = false,
int? maxMinutes,
bool highlightsOnly = false}) {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ToursPage(
initialCategory: category,
initialTopicSlug: topicSlug,
initialFamilyOnly: familyOnly,
initialMaxMinutes: maxMinutes,
initialHighlightsOnly: highlightsOnly,
),
));
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final lang = Localizations.localeOf(context).languageCode;
final store = AppDataScope.of(context);
final featured = store.tours.where((tour) => tour.highlight).toList();
final homeAd = store.campaignFor(AdPlacement.homeBanner);
final genericAd = store.campaignFor(AdPlacement.appBanner);
final ad = homeAd ??
(genericAd?.supportsScreen('home') == true ? genericAd : null);
return SafeArea(
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.fromLTRB(20, 18, 20, 12),
sliver: SliverList.list(children: [
const FirstRunOnboardingTrigger(),
const TourguideBrandHeader(),
const SizedBox(height: 8),
Builder(
builder: (context) {
final location = LocationScope.of(context);
if (location.status == AppLocationStatus.loading) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
children: [
const SizedBox.square(
dimension: 15,
child: CircularProgressIndicator(strokeWidth: 2),
),
const SizedBox(width: 8),
Text(
l10n.t('locationLoading'),
style: const TextStyle(
color: AppColors.mutedInk,
fontSize: 12,
),
),
],
),
);
}
if (location.status == AppLocationStatus.unavailable) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
children: [
const Icon(
Icons.location_off_outlined,
size: 17,
color: AppColors.mutedInk,
),
const SizedBox(width: 6),
Expanded(
child: Text(
l10n.t('locationUnavailable'),
style: TextStyle(
color: AppColors.mutedInk,
fontSize: 12,
),
),
),
TextButton(
onPressed: location.refresh,
child: Text(l10n.t('retry')),
),
],
),
);
}
return const SizedBox.shrink();
},
),
if (store.usingCachedData) ...[
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
color: AppColors.softRed,
borderRadius: BorderRadius.circular(14),
),
child: Row(
children: [
const Icon(Icons.offline_pin_outlined,
size: 18, color: AppColors.heartRed),
SizedBox(width: 8),
Expanded(
child: Text(
l10n.t('offlineMode'),
style: TextStyle(fontWeight: FontWeight.w800),
),
),
],
),
),
const SizedBox(height: 14),
],
const SizedBox(height: 22),
Text(l10n.t('hello'),
style: Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(fontWeight: FontWeight.w800)),
const SizedBox(height: 6),
Text(l10n.t('subtitle'),
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(color: AppColors.mutedInk)),
const SizedBox(height: 22),
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: AppColors.ink,
borderRadius: BorderRadius.circular(24)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
const Icon(Icons.auto_awesome, color: Colors.white),
const SizedBox(width: 8),
Text(l10n.t('individualTour'),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800))
]),
const SizedBox(height: 9),
Text(l10n.t('individualTourTeaser'),
style: TextStyle(
color: Colors.white.withValues(alpha: .82))),
const SizedBox(height: 15),
FilledButton.tonal(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const RoutePlannerPage())),
child: Text(l10n.t('individualTour')),
),
]),
),
const SizedBox(height: 18),
Material(
color: AppColors.softRed,
borderRadius: BorderRadius.circular(22),
child: InkWell(
borderRadius: BorderRadius.circular(22),
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => const MapPage())),
child: Padding(
padding: const EdgeInsets.all(18),
child: Row(children: [
CircleAvatar(
radius: 26,
backgroundColor: AppColors.heartRed,
child: Icon(Icons.map_rounded, color: Colors.white)),
SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.t('interactiveMap'),
style: TextStyle(
fontWeight: FontWeight.w800, fontSize: 17)),
SizedBox(height: 4),
Text(l10n.t('interactiveMapTeaser')),
])),
Icon(Icons.arrow_forward_rounded),
]),
),
),
),
const SizedBox(height: 18),
LayoutBuilder(builder: (context, constraints) {
// Use the width of the padded home content, not a device class.
// A regular iPhone leaves roughly 350 logical pixels here and
// should still show both shortcuts in one row.
final stackCards = constraints.maxWidth < 300 ||
MediaQuery.textScalerOf(context).scale(16) > 21;
final learn = _FeatureCard(
icon: Icons.translate_rounded,
title: l10n.t('learnGerman'),
subtitle: l10n.t('learnGermanShort'),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => const LearningHomePage())));
final arrival = _FeatureCard(
icon: Icons.flight_land_rounded,
title: l10n.t('newInWeimar'),
subtitle: l10n.t('newInWeimarShort'),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => const NewInWeimarPage())));
if (stackCards) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [learn, const SizedBox(height: 12), arrival]);
}
return Row(children: [
Expanded(child: learn),
const SizedBox(width: 12),
Expanded(child: arrival)
]);
}),
if (ad != null) ...[
const SizedBox(height: 18),
SponsoredBanner(campaign: ad, placement: 'home_banner'),
],
const SizedBox(height: 26),
SectionHeader(title: l10n.t('quick')),
const SizedBox(height: 12),
Wrap(spacing: 8, runSpacing: 8, children: [
_QuickChip(
icon: Icons.timer_outlined,
label: l10n.t('max90'),
onTap: () => _openTours(context, maxMinutes: 90)),
_QuickChip(
icon: Icons.auto_awesome,
label: l10n.t('highlights'),
onTap: () => _openTours(context, highlightsOnly: true)),
_QuickChip(
icon: Icons.history_edu_outlined,
label: l10n.t('classic'),
onTap: () => _openTours(context, topicSlug: 'klassik')),
_QuickChip(
icon: Icons.menu_book_rounded,
label: 'Goethe',
onTap: () => _openTours(context, topicSlug: 'goethe')),
_QuickChip(
icon: Icons.architecture,
label: 'Bauhaus',
onTap: () => _openTours(context, topicSlug: 'bauhaus')),
_QuickChip(
icon: Icons.account_balance_rounded,
label: l10n.t('palaces'),
onTap: () => _openTours(context, topicSlug: 'schloesser')),
_QuickChip(
icon: Icons.history_rounded,
label: 'Buchenwald',
onTap: () => _openTours(context, topicSlug: 'buchenwald')),
_QuickChip(
icon: Icons.family_restroom,
label: l10n.t('family'),
onTap: () => _openTours(context, familyOnly: true)),
_QuickChip(
icon: Icons.park_rounded,
label: l10n.t('playgrounds'),
onTap: () =>
_openTours(context, topicSlug: 'spielplaetze')),
]),
const SizedBox(height: 30),
const HomeEventsSection(),
const SizedBox(height: 30),
const HomeNowSection(),
const SizedBox(height: 30),
SectionHeader(title: l10n.t('featured')),
const SizedBox(height: 12),
]),
),
SliverToBoxAdapter(
child: SizedBox(
height:
(MediaQuery.textScalerOf(context).scale(360)).clamp(360, 460),
child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 20),
scrollDirection: Axis.horizontal,
itemCount: featured.length,
separatorBuilder: (_, __) => const SizedBox(width: 14),
itemBuilder: (context, index) => TourCard(
compact: true,
tour: featured[index],
languageCode: lang,
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => TourDetailPage(tour: featured[index]))),
),
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 28)),
],
),
);
}
}
class _QuickChip extends StatelessWidget {
const _QuickChip(
{required this.icon, required this.label, required this.onTap});
final IconData icon;
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return ActionChip(
avatar: Icon(icon, size: 18), label: Text(label), onPressed: onTap);
}
}
class _FeatureCard extends StatelessWidget {
const _FeatureCard(
{required this.icon,
required this.title,
required this.subtitle,
required this.onTap});
final IconData icon;
final String title;
final String subtitle;
final VoidCallback onTap;
@override
Widget build(BuildContext context) => Material(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: AppColors.heartRed, size: 28),
const SizedBox(height: 10),
Text(title,
style: const TextStyle(fontWeight: FontWeight.w900)),
const SizedBox(height: 4),
Text(subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.mutedInk, fontSize: 12))
]))));
}

