diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/main.dart b/lib/main.dart index 7650ff5..7204f0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,36 +1,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; import 'package:resume/pages/landing_page.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:resume/providers/locale_provider.dart'; +import 'package:resume/providers/content_provider.dart'; import 'theme.dart' show darkTheme; void main() { - runApp(const Resume()); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => LocaleProvider()), + ChangeNotifierProvider(create: (context) => ContentProvider()), + ], + child: const Resume(), + ), + ); } -class Resume extends StatelessWidget { +class Resume extends StatefulWidget { const Resume({super.key}); + @override + State createState() => _ResumeState(); +} + +class _ResumeState extends State { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Resume', - theme: darkTheme, - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en'), - Locale('de'), - ], - locale: const Locale('en'), - routes: { - '/': (context) => const LandingPage(), - }, - initialRoute: '/', + return Consumer( + builder: (context, localeProvider, child) => MaterialApp( + title: 'Resume', + theme: darkTheme, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en'), + Locale('de'), + ], + locale: localeProvider.locale, + routes: { + '/': (context) => const LandingPage(), + }, + initialRoute: '/', + ), ); } } diff --git a/lib/pages/landing_page.dart b/lib/pages/landing_page.dart index 9781e3f..a1686bc 100644 --- a/lib/pages/landing_page.dart +++ b/lib/pages/landing_page.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:resume/providers/locale_provider.dart'; import 'package:resume/services/breakpoints.dart'; import 'package:resume/widgets/language_dropdown.dart'; import 'package:resume/widgets/profile.dart'; @@ -6,7 +8,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:resume/constants.dart' show ContentType; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../services/content_provider.dart'; +import '../providers/content_provider.dart'; import '../widgets/content_block.dart'; class LandingPage extends StatefulWidget { @@ -20,14 +22,18 @@ class LandingPage extends StatefulWidget { class _LandingPageState extends State { bool loadingDone = false; + late ContentProvider contentProvider; + late Locale currentLocale; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - await ContentProvider.init(context); + await Provider.of(context, listen: false) + .loadContent(context); setState(() => loadingDone = true); }); + currentLocale = context.read().locale; } double _getMainContentWidth() { @@ -49,11 +55,13 @@ class _LandingPageState extends State { ContentBlock( blockTitle: AppLocalizations.of(context)!.skills, contentType: ContentType.skills, + content: contentProvider.getContent(ContentType.skills), ), const Padding(padding: EdgeInsets.only(bottom: 25)), ContentBlock( blockTitle: AppLocalizations.of(context)!.languages, contentType: ContentType.language, + content: contentProvider.getContent(ContentType.language), ), ], ); @@ -65,21 +73,25 @@ class _LandingPageState extends State { ContentBlock( blockTitle: AppLocalizations.of(context)!.work_experience, contentType: ContentType.experience, + content: contentProvider.getContent(ContentType.experience), ), const Padding(padding: EdgeInsets.only(bottom: 25)), ContentBlock( blockTitle: AppLocalizations.of(context)!.education, contentType: ContentType.education, + content: contentProvider.getContent(ContentType.education), ), const Padding(padding: EdgeInsets.only(bottom: 25)), ContentBlock( blockTitle: AppLocalizations.of(context)!.additional_skills, contentType: ContentType.generalSkills, + content: contentProvider.getContent(ContentType.generalSkills), ), const Padding(padding: EdgeInsets.only(bottom: 25)), ContentBlock( blockTitle: AppLocalizations.of(context)!.about_me, contentType: ContentType.text, + content: contentProvider.getContent(ContentType.text), ), ], ); @@ -188,6 +200,20 @@ class _LandingPageState extends State { @override Widget build(BuildContext context) { + contentProvider = context.read(); + + if (currentLocale != context.read().locale) { + loadingDone = false; + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Provider.of(context, listen: false) + .loadContent(context); + setState(() { + currentLocale = context.read().locale; + loadingDone = true; + }); + }); + } + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.resume), diff --git a/lib/providers/content_provider.dart b/lib/providers/content_provider.dart new file mode 100644 index 0000000..ae7c802 --- /dev/null +++ b/lib/providers/content_provider.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:resume/constants.dart'; +import 'package:resume/providers/locale_provider.dart'; + +class ContentProvider extends ChangeNotifier { + static final ContentProvider _instance = ContentProvider._(); + factory ContentProvider() => _instance; + ContentProvider._(); + + static const String _baseJsonPath = 'assets/content/content'; + + Map _content = { + 'experience': >[], + 'education': >[], + 'skills': >[], + 'text': [], + 'general_skills': [], + }; + + Future loadContent(BuildContext context) async { + String currentLocale = context.read().locale.languageCode; + final String localizedPath = '${_baseJsonPath}_$currentLocale.json'; + + try { + String file = await rootBundle.loadString(localizedPath); + _content = json.decode(file); + notifyListeners(); + return true; + } catch (e) { + // If localized version fails, fall back to default German + String file = await rootBundle.loadString('${_baseJsonPath}_de.json'); + _content = json.decode(file); + notifyListeners(); + return true; + } + } + + T getContent(ContentType contentType) { + switch (contentType) { + case ContentType.experience: + return _content['experience'] as T; + case ContentType.education: + return _content['education'] as T; + case ContentType.skills: + return _content['skills'] as T; + case ContentType.text: + return _content['text'] as T; + case ContentType.generalSkills: + return _content['general_skills'] as T; + default: + return [] as T; + } + } +} diff --git a/lib/providers/locale_provider.dart b/lib/providers/locale_provider.dart new file mode 100644 index 0000000..b2ddfde --- /dev/null +++ b/lib/providers/locale_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class LocaleProvider extends ChangeNotifier { + Locale _locale = const Locale('de'); + + Locale get locale => _locale; + + void setLocale(Locale locale) { + _locale = locale; + notifyListeners(); + } +} diff --git a/lib/services/content_provider.dart b/lib/services/content_provider.dart deleted file mode 100644 index a649520..0000000 --- a/lib/services/content_provider.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:resume/constants.dart'; - -class ContentProvider { - ContentProvider._(); - - static const String _baseJsonPath = 'assets/content/content'; - - static Future init(BuildContext context) async { - try { - // Get the current locale - final String currentLocale = Localizations.localeOf(context).languageCode; - - // Construct the path with the locale - final String localizedPath = '${_baseJsonPath}_$currentLocale.json'; - - // Try to load the localized version first - try { - String file = await rootBundle.loadString(localizedPath); - _content = json.decode(file); - return true; - } catch (e) { - // If localized version fails, fall back to default English - String file = await rootBundle.loadString('${_baseJsonPath}_de.json'); - _content = json.decode(file); - return true; - } - } catch (e) { - print('Error loading content: $e'); - return false; - } - } - - static Map _content = { - 'experience': >[], - 'education': >[], - 'skills': >[], - 'text': [], - 'general_skills': [], - }; - - static T getContent(ContentType contentType) { - switch (contentType) { - case ContentType.experience: - return _content['experience'] as T; - case ContentType.education: - return _content['education'] as T; - case ContentType.skills: - return _content['skills'] as T; - case ContentType.text: - return _content['text'] as T; - case ContentType.generalSkills: - return _content['general_skills'] as T; - default: - return [] as T; - } - } -} diff --git a/lib/widgets/content_block.dart b/lib/widgets/content_block.dart index 6437755..b7d5def 100644 --- a/lib/widgets/content_block.dart +++ b/lib/widgets/content_block.dart @@ -3,32 +3,29 @@ import 'package:resume/widgets/content_list_tile.dart'; import 'package:resume/widgets/language_widget.dart'; import 'package:resume/constants.dart' show ContentType; -import '../services/content_provider.dart'; - class ContentBlock extends StatelessWidget { const ContentBlock({ super.key, required this.blockTitle, required this.contentType, + required this.content, }); final String blockTitle; final ContentType contentType; + final dynamic content; Widget get _getContentWidget { if (contentType == ContentType.language) { return const LanguageWidget(); } else if (contentType == ContentType.text || contentType == ContentType.generalSkills) { - final content = ContentProvider.getContent(contentType); return SizedBox( width: double.infinity, child: Text(content), ); } // List-based content-blocks - List content = - ContentProvider.getContent>(contentType); List widgets = []; for (var item in content) { widgets.add(_buildListTile(item)); diff --git a/lib/widgets/language_dropdown.dart b/lib/widgets/language_dropdown.dart index 1cec262..d506622 100644 --- a/lib/widgets/language_dropdown.dart +++ b/lib/widgets/language_dropdown.dart @@ -1,13 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; -class LanguageDropdown extends StatelessWidget { +import '../providers/locale_provider.dart'; + +class LanguageDropdown extends StatefulWidget { const LanguageDropdown({super.key}); + @override + State createState() => _LanguageDropdownState(); +} + +class _LanguageDropdownState extends State { @override Widget build(BuildContext context) { - return DropdownButton( - value: 'de', + LocaleProvider localeProvider = Provider.of(context); + return DropdownButton( + value: localeProvider.locale.languageCode, items: [ DropdownMenuItem( value: 'de', @@ -24,12 +33,13 @@ class LanguageDropdown extends StatelessWidget { ), ), ], - onChanged: _onChanged, + onChanged: (value) { + if (value == null) return; + localeProvider.setLocale(Locale(value)); + }, ); } - void _onChanged(dynamic value) {} - Widget getMenuItem(String label, String imagePath) { return Row( children: [ diff --git a/pubspec.yaml b/pubspec.yaml index 829f315..58f8164 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: flutter_localizations: sdk: flutter intl: any + provider: ^6.1.2 dev_dependencies: flutter_test: