import 'dart:async';
import 'package:flutter/material.dart';
import '../../l10n/app_localizations.dart';
import '../../l10n/locale_controller.dart';
import '../../core/location/location_controller.dart';
import '../../core/theme/app_theme.dart';
class OnboardingPage extends StatefulWidget {
const OnboardingPage({
super.key,
required this.localeController,
required this.locationController,
});
final LocaleController localeController;
final LocationController locationController;
@override
State<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
late String _languageTag = localeLanguageTag(widget.localeController.locale);
bool _continuing = false;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 720;
final welcome = _WelcomePanel(l10n: l10n);
final setup = _SetupPanel(
l10n: l10n,
languageTag: _languageTag,
onLanguageChanged: (value) =>
setState(() => _languageTag = value ?? _languageTag),
onContinue: () async {
if (_continuing) return;
setState(() => _continuing = true);
await widget.localeController.select(
localeFromLanguageTag(_languageTag) ??
widget.localeController.locale,
);
// The explanation above is shown before the platform prompt.
// A denial is intentionally non-blocking: the app remains
// fully usable with address and category search.
// Geolocation is an optional enhancement. Do not hold the
// onboarding button or the first app paint open while a
// browser/WebView permission promise is pending.
unawaited(widget.locationController.requestLocation());
unawaited(
widget.locationController.markStartupPromptCompleted(),
);
await widget.localeController.completeOnboarding();
if (mounted) setState(() => _continuing = false);
},
localeLabel: _localeLabel,
continuing: _continuing,
);
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1040),
child: wide
? IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: welcome),
const SizedBox(width: 24),
Expanded(child: setup),
],
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
welcome,
const SizedBox(height: 16),
setup,
],
),
),
),
);
},
),
),
);
}
String _localeLabel(AppLocalizations l10n, Locale locale) {
return switch (locale.languageCode.toLowerCase()) {
'de' => l10n.languageGerman,
'en' => l10n.languageEnglish,
_ => localeLanguageTag(locale),
};
}
}
class _WelcomePanel extends StatelessWidget {
const _WelcomePanel({required this.l10n});
final AppLocalizations l10n;
@override
Widget build(BuildContext context) {
return Card(
color: AppTheme.palette(context).ink,
child: Padding(
padding: const EdgeInsets.all(28),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.explore,
color: AppTheme.palette(context).brand,
size: 64,
),
const SizedBox(height: 24),
Text(
l10n.onboardingTitle,
style: Theme.of(
context,
).textTheme.headlineMedium?.copyWith(color: Colors.white),
),
const SizedBox(height: 12),
Text(
l10n.onboardingBody,
style: const TextStyle(color: Colors.white70, height: 1.5),
),
],
),
),
);
}
}
class _SetupPanel extends StatelessWidget {
const _SetupPanel({
required this.l10n,
required this.languageTag,
required this.onLanguageChanged,
required this.onContinue,
required this.localeLabel,
required this.continuing,
});
final AppLocalizations l10n;
final String languageTag;
final ValueChanged<String?> onLanguageChanged;
final VoidCallback onContinue;
final String Function(AppLocalizations, Locale) localeLabel;
final bool continuing;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
l10n.languageLabel,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
initialValue: languageTag,
decoration: InputDecoration(
labelText: l10n.languageLabel,
prefixIcon: const Icon(Icons.translate_outlined),
),
items: AppLocalizations.supportedLocales
.map(
(locale) => DropdownMenuItem(
value: localeLanguageTag(locale),
child: Text(localeLabel(l10n, locale)),
),
)
.toList(growable: false),
onChanged: onLanguageChanged,
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.location_on_outlined,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.locationPermissionTitle,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(l10n.locationPermissionBody),
const SizedBox(height: 8),
Text(
l10n.locationFallback,
style: TextStyle(
color: AppTheme.palette(context).muted,
),
),
],
),
),
],
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(14),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.cloud_off_outlined, size: 20),
const SizedBox(width: 10),
Expanded(child: Text(l10n.onboardingOfflineBody)),
],
),
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: continuing ? null : onContinue,
icon: continuing
? const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.arrow_forward),
label: Text(continuing ? l10n.loadingLabel : l10n.continueLabel),
),
],
),
),
);
}
}
