import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/date_symbol_data_local.dart' as date_data;
import 'core/config/app_config.dart';
import 'core/location/location_controller.dart';
import 'core/navigation/browser_location.dart';
import 'core/networking/api_client.dart';
import 'core/theme/app_theme.dart';
import 'features/onboarding/onboarding_page.dart';
import 'l10n/app_localizations.dart';
import 'l10n/backend_localizations.dart';
import 'l10n/locale_controller.dart';
import 'repositories/http_discovery_repository.dart';
import 'repositories/http_account_repository.dart';
import 'repositories/http_claim_repository.dart';
import 'repositories/http_geo_repository.dart';
import 'repositories/http_image_suggestion_repository.dart';
import 'repositories/http_notifications_repository.dart';
import 'services/bootstrap_service.dart';
import 'shared/app_shell/app_shell.dart';
import 'shared/states/app_state_widgets.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Browser/WebView startup must not depend indefinitely on a plugin or
// storage promise. The app can still render its localized fallback/onboarding
// surface when either optional startup preparation is unavailable.
try {
await date_data.initializeDateFormatting().timeout(
const Duration(seconds: 3),
);
} catch (_) {
// Date formatting remains best-effort; the normal app shell must start.
}
final supportedLocales = AppLocalizations.supportedLocales;
final localeController = LocaleController();
try {
await localeController
.load(supported: supportedLocales)
.timeout(const Duration(seconds: 3));
} catch (_) {
// A stalled private-WebView storage must not leave a blank Flutter view.
}
localeController.resolveFromSystem(
WidgetsBinding.instance.platformDispatcher.locales,
supported: supportedLocales,
);
runApp(MeinWeimarApp(localeController: localeController));
}
class MeinWeimarApp extends StatefulWidget {
MeinWeimarApp({
super.key,
required this.localeController,
LocationController? locationController,
}) : _locationController = locationController,
_environmentConfig = AppConfig.fromEnvironment();
final LocationController? _locationController;
final LocaleController localeController;
final AppConfig _environmentConfig;
@override
State<MeinWeimarApp> createState() => _MeinWeimarAppState();
}
class _MeinWeimarAppState extends State<MeinWeimarApp> {
late final LocationController _locationController =
widget._locationController ?? LocationController();
late final ApiClient _apiClient = ApiClient(
baseUrl: widget._environmentConfig.apiBaseUrl,
locale: () => widget.localeController.locale.toLanguageTag(),
);
@override
void dispose() {
_locationController.dispose();
_apiClient.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final initialPath = _initialAppPath();
return AnimatedBuilder(
animation: widget.localeController,
builder: (context, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
scrollBehavior: const _MeinWeimarScrollBehavior(),
onGenerateTitle: (context) => AppLocalizations.of(context).appTitle,
locale: widget.localeController.locale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: AppTheme.light(
primary: Color(widget._environmentConfig.brand.primaryColor),
),
home: _BootstrapGate(
localeController: widget.localeController,
locationController: _locationController,
apiClient: _apiClient,
initialPath: initialPath,
),
);
},
);
}
String _initialAppPath() {
final platformPath =
WidgetsBinding.instance.platformDispatcher.defaultRouteName;
final browserUrl = browserLocationHref();
if (kIsWeb && browserUrl.isNotEmpty) {
// Keep the fragment when an older hash-based Flutter web deployment is
// used; CanonicalModuleRoutes resolves both path and hash forms. Uri.base
// can resolve to the document <base> URI on web, which loses a direct
// history-API path such as /immobilien/.
return browserUrl;
}
return platformPath;
}
}
class _MeinWeimarScrollBehavior extends MaterialScrollBehavior {
const _MeinWeimarScrollBehavior();
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
PointerDeviceKind.mouse,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
};
@override
ScrollPhysics getScrollPhysics(BuildContext context) =>
kIsWeb ? const ClampingScrollPhysics() : super.getScrollPhysics(context);
}
class _BootstrapGate extends StatefulWidget {
const _BootstrapGate({
required this.localeController,
required this.locationController,
required this.apiClient,
this.initialPath,
});
final LocaleController localeController;
final LocationController locationController;
final ApiClient apiClient;
final String? initialPath;
@override
State<_BootstrapGate> createState() => _BootstrapGateState();
}
class _BootstrapGateState extends State<_BootstrapGate> {
late Future<BootstrapSnapshot> _bootstrap;
@override
void initState() {
super.initState();
_bootstrap = _loadBootstrap();
}
Future<BootstrapSnapshot> _loadBootstrap() {
return BootstrapService(widget.apiClient).load();
}
void _retryBootstrap() {
setState(() => _bootstrap = _loadBootstrap());
}
@override
Widget build(BuildContext context) {
if (!widget.localeController.onboardingComplete) {
return OnboardingPage(
localeController: widget.localeController,
locationController: widget.locationController,
);
}
return FutureBuilder<BootstrapSnapshot>(
future: _bootstrap,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Scaffold(body: LoadingState());
final bootstrap = snapshot.data!;
return Localizations(
locale: Localizations.localeOf(context),
delegates: <LocalizationsDelegate<dynamic>>[
BackendLocalizationsDelegate(bootstrap.config.appTexts),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
child: AppShell(
snapshot: bootstrap,
localeController: widget.localeController,
locationController: widget.locationController,
apiClient: widget.apiClient,
initialPath: widget.initialPath,
repository: HttpDiscoveryRepository(widget.apiClient),
accountRepository: HttpAccountRepository(widget.apiClient),
claimRepository: HttpClaimRepository(widget.apiClient),
geoRepository: HttpGeoRepository(widget.apiClient),
imageSuggestionRepository: HttpImageSuggestionRepository(
widget.apiClient,
),
notificationsRepository: HttpNotificationsRepository(
widget.apiClient,
),
onRetryBootstrap: _retryBootstrap,
),
);
},
);
}
}
