Compare commits
16 Commits
c62dbbb707
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b66da23887 | |||
| 6587828a0b | |||
| a984fc15b0 | |||
| 0d9118ab3e | |||
| 85b5d4dc7c | |||
| ebf1246a55 | |||
| 8e91388ecf | |||
| b9033014c8 | |||
| 0a877525aa | |||
| ff97898b90 | |||
| c523b7495f | |||
| 4c2162a158 | |||
| ee705938f7 | |||
| 35e1171623 | |||
| 6ebfa77416 | |||
| b8840ffe0c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -156,4 +156,4 @@ app.*.symbols
|
||||
!.vscode/settings.json
|
||||
|
||||
# Custom
|
||||
content.json
|
||||
content/
|
||||
BIN
assets/fonts/radley/Radley-Italic.ttf
Normal file
BIN
assets/fonts/radley/Radley-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/radley/Radley-Regular.ttf
Normal file
BIN
assets/fonts/radley/Radley-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@@ -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:
|
||||
3
l10n.yaml
Normal file
3
l10n.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
||||
@@ -1,17 +1,8 @@
|
||||
/// List of months 0-11
|
||||
const months = [
|
||||
'Januar',
|
||||
'Februar',
|
||||
'März',
|
||||
'April',
|
||||
'Mai',
|
||||
'Juni',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'Oktober',
|
||||
'November',
|
||||
'Dezember'
|
||||
];
|
||||
|
||||
enum ContentType { experience, education, skills, language, text }
|
||||
enum ContentType {
|
||||
experience,
|
||||
education,
|
||||
skills,
|
||||
language,
|
||||
text,
|
||||
generalSkills
|
||||
}
|
||||
|
||||
28
lib/l10n/app_de.arb
Normal file
28
lib/l10n/app_de.arb
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"german": "Deutsch",
|
||||
"english": "Englisch",
|
||||
"resume": "Lebenslauf",
|
||||
"skills": "Fähigkeiten",
|
||||
"languages": "Sprachen",
|
||||
"mother_tongue": "Muttersprache",
|
||||
"very_good": "Sehr gut",
|
||||
"about_me": "Über mich",
|
||||
"additional_skills": "Weitere Kenntnisse",
|
||||
"work_experience": "Arbeitserfahrung",
|
||||
"education": "Bildungsweg",
|
||||
"source_code": "Quellcode",
|
||||
|
||||
"@_months": {},
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
}
|
||||
28
lib/l10n/app_en.arb
Normal file
28
lib/l10n/app_en.arb
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"german": "German",
|
||||
"english": "English",
|
||||
"resume": "Resume",
|
||||
"skills": "Skills",
|
||||
"languages": "Languages",
|
||||
"mother_tongue": "Native",
|
||||
"very_good": "Very good",
|
||||
"about_me": "About me",
|
||||
"additional_skills": "Additional Skills",
|
||||
"work_experience": "Work experience",
|
||||
"education": "Education",
|
||||
"source_code": "Source Code",
|
||||
|
||||
"@_months": {},
|
||||
"january": "January",
|
||||
"february": "February",
|
||||
"march": "March",
|
||||
"april": "April",
|
||||
"may": "May",
|
||||
"june": "June",
|
||||
"july": "July",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "October",
|
||||
"november": "November",
|
||||
"december": "December"
|
||||
}
|
||||
@@ -1,26 +1,60 @@
|
||||
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 './services/tools.dart';
|
||||
import 'theme.dart' show darkTheme;
|
||||
|
||||
void main() {
|
||||
runApp(const Resume());
|
||||
final String initialPath = Uri.base.path;
|
||||
final String defaultLocale = Tools.getLocaleFromPath(initialPath);
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) =>
|
||||
LocaleProvider(defaultLocale: Locale(defaultLocale)),
|
||||
),
|
||||
ChangeNotifierProvider(create: (context) => ContentProvider()),
|
||||
],
|
||||
child: const Resume(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Resume extends StatelessWidget {
|
||||
class Resume extends StatefulWidget {
|
||||
const Resume({super.key});
|
||||
|
||||
@override
|
||||
State<Resume> createState() => _ResumeState();
|
||||
}
|
||||
|
||||
class _ResumeState extends State<Resume> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Resume',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
||||
useMaterial3: true,
|
||||
return Consumer<LocaleProvider>(
|
||||
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: '/',
|
||||
),
|
||||
routes: {
|
||||
'/': (context) => const LandingPage(),
|
||||
},
|
||||
initialRoute: '/',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
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';
|
||||
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 {
|
||||
@@ -18,14 +22,18 @@ class LandingPage extends StatefulWidget {
|
||||
|
||||
class _LandingPageState extends State<LandingPage> {
|
||||
bool loadingDone = false;
|
||||
late ContentProvider contentProvider;
|
||||
late Locale currentLocale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await ContentProvider.init();
|
||||
await Provider.of<ContentProvider>(context, listen: false)
|
||||
.loadContent(context);
|
||||
setState(() => loadingDone = true);
|
||||
});
|
||||
currentLocale = context.read<LocaleProvider>().locale;
|
||||
}
|
||||
|
||||
double _getMainContentWidth() {
|
||||
@@ -42,32 +50,48 @@ class _LandingPageState extends State<LandingPage> {
|
||||
(MediaQuery.of(context).size.width - _getMainContentWidth()) / 2;
|
||||
|
||||
Widget _getSideBar() {
|
||||
return const Column(
|
||||
return Column(
|
||||
children: [
|
||||
ContentBlock(
|
||||
blockTitle: 'Fähigkeiten',
|
||||
blockTitle: AppLocalizations.of(context)!.skills,
|
||||
contentType: ContentType.skills,
|
||||
content: contentProvider.getContent(ContentType.skills),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||
ContentBlock(
|
||||
blockTitle: 'Sprachen',
|
||||
blockTitle: AppLocalizations.of(context)!.languages,
|
||||
contentType: ContentType.language,
|
||||
content: contentProvider.getContent(ContentType.language),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getMainContent() {
|
||||
return const Column(
|
||||
return Column(
|
||||
children: [
|
||||
ContentBlock(
|
||||
blockTitle: 'Arbeitserfahrung',
|
||||
blockTitle: AppLocalizations.of(context)!.work_experience,
|
||||
contentType: ContentType.experience,
|
||||
content: contentProvider.getContent(ContentType.experience),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||
ContentBlock(
|
||||
blockTitle: 'Bildungsweg',
|
||||
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),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -176,11 +200,28 @@ class _LandingPageState extends State<LandingPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
contentProvider = context.read<ContentProvider>();
|
||||
|
||||
if (currentLocale != context.read<LocaleProvider>().locale) {
|
||||
loadingDone = false;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await Provider.of<ContentProvider>(context, listen: false)
|
||||
.loadContent(context);
|
||||
setState(() {
|
||||
currentLocale = context.read<LocaleProvider>().locale;
|
||||
loadingDone = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Landing'),
|
||||
actions: const [
|
||||
TextButton(onPressed: _launchURL, child: Text('Source Code')),
|
||||
title: Text(AppLocalizations.of(context)!.resume),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _launchURL,
|
||||
child: Text(AppLocalizations.of(context)!.source_code)),
|
||||
const LanguageDropdown(),
|
||||
],
|
||||
),
|
||||
body: !loadingDone
|
||||
|
||||
58
lib/providers/content_provider.dart
Normal file
58
lib/providers/content_provider.dart
Normal file
@@ -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<String, dynamic> _content = {
|
||||
'experience': <List<dynamic>>[],
|
||||
'education': <List<dynamic>>[],
|
||||
'skills': <List<dynamic>>[],
|
||||
'text': <String>[],
|
||||
'general_skills': <String>[],
|
||||
};
|
||||
|
||||
Future<bool> loadContent(BuildContext context) async {
|
||||
String currentLocale = context.read<LocaleProvider>().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<T>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
lib/providers/locale_provider.dart
Normal file
14
lib/providers/locale_provider.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LocaleProvider extends ChangeNotifier {
|
||||
LocaleProvider({Locale? defaultLocale})
|
||||
: _locale = defaultLocale ?? const Locale('de');
|
||||
Locale _locale = const Locale('de');
|
||||
|
||||
Locale get locale => _locale;
|
||||
|
||||
void setLocale(Locale locale) {
|
||||
_locale = locale;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:resume/constants.dart';
|
||||
|
||||
class ContentProvider {
|
||||
ContentProvider._();
|
||||
|
||||
static const String _jsonPath = 'assets/content.json';
|
||||
|
||||
static Future<bool> init() async {
|
||||
try {
|
||||
String file = await rootBundle.loadString(_jsonPath);
|
||||
_content = json.decode(file);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _content = {
|
||||
'experience': <List<dynamic>>[],
|
||||
'education': <List<dynamic>>[],
|
||||
'skills': <List<dynamic>>[],
|
||||
'text': <String>[],
|
||||
};
|
||||
|
||||
static T getContent<T>(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;
|
||||
default:
|
||||
return [] as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,108 @@
|
||||
import 'package:resume/constants.dart' show months;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class Tools {
|
||||
/// builds timespan-String out of two date-Strings.
|
||||
/// Date has to be formatted as yyyy-MM
|
||||
static buildTimeString(String startDate, String endDate) {
|
||||
if (startDate.isEmpty || endDate.isEmpty) return '';
|
||||
final firstDate = DateTime.parse('$startDate-01');
|
||||
final secondDate = DateTime.parse('$endDate-01');
|
||||
return '${months[firstDate.month - 1]} ${firstDate.year} - ${months[secondDate.month - 1]} ${secondDate.year}';
|
||||
Tools._();
|
||||
|
||||
/// Builds a formatted time string from two date strings in the format 'YYYY-MM'.
|
||||
///
|
||||
/// Returns a string in the format 'Month Year - Month Year' using localized month names.
|
||||
/// Returns an empty string if either input is empty or invalid.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [startDate]: String in 'YYYY-MM' format (e.g., '2024-03')
|
||||
/// - [endDate]: String in 'YYYY-MM' format (e.g., '2024-12')
|
||||
/// - [context]: BuildContext for accessing localizations
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final timeString = buildTimeString('2024-03', '2024-12', context);
|
||||
/// // Returns "March 2024 - December 2024" (or localized equivalent)
|
||||
/// ```
|
||||
static String buildTimeString(
|
||||
String startDate, String endDate, BuildContext context) {
|
||||
// Check for empty or null inputs
|
||||
if (startDate.isEmpty || endDate.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate date format
|
||||
if (!startDate.contains(RegExp(r'^\d{4}-\d{2}$')) ||
|
||||
!endDate.contains(RegExp(r'^\d{4}-\d{2}$'))) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Parse dates with validation
|
||||
final firstDate = DateTime.tryParse('$startDate-01');
|
||||
final secondDate = DateTime.tryParse('$endDate-01');
|
||||
|
||||
// Check if parsing was successful
|
||||
if (firstDate == null || secondDate == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Build the formatted string
|
||||
return '${getLocalizedMonth(context, firstDate.month)} ${firstDate.year} - '
|
||||
'${getLocalizedMonth(context, secondDate.month)} ${secondDate.year}';
|
||||
} catch (e) {
|
||||
// Handle any unexpected errors
|
||||
debugPrint('Error building time string: $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the localized month name as a [String] based on the provided month number.
|
||||
///
|
||||
/// The [context] is used to access the app's localizations.
|
||||
/// The [monthNumber] must be between 1 and 12, where 1 represents January
|
||||
/// and 12 represents December.
|
||||
///
|
||||
/// Throws an [ArgumentError] if the month number is not between 1 and 12.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final monthName = getLocalizedMonth(context, 3); // Returns "March" or "März"
|
||||
/// ```
|
||||
static String getLocalizedMonth(BuildContext context, int monthNumber) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
switch (monthNumber) {
|
||||
case 1:
|
||||
return localizations.january;
|
||||
case 2:
|
||||
return localizations.february;
|
||||
case 3:
|
||||
return localizations.march;
|
||||
case 4:
|
||||
return localizations.april;
|
||||
case 5:
|
||||
return localizations.may;
|
||||
case 6:
|
||||
return localizations.june;
|
||||
case 7:
|
||||
return localizations.july;
|
||||
case 8:
|
||||
return localizations.august;
|
||||
case 9:
|
||||
return localizations.september;
|
||||
case 10:
|
||||
return localizations.october;
|
||||
case 11:
|
||||
return localizations.november;
|
||||
case 12:
|
||||
return localizations.december;
|
||||
default:
|
||||
throw ArgumentError('Month number must be between 1 and 12');
|
||||
}
|
||||
}
|
||||
|
||||
static String getLocaleFromPath(String path) {
|
||||
if (path.startsWith('/de')) {
|
||||
return 'de';
|
||||
} else if (path.startsWith('/en')) {
|
||||
return 'en';
|
||||
}
|
||||
return 'de'; // Default fallback
|
||||
}
|
||||
}
|
||||
|
||||
10
lib/theme.dart
Normal file
10
lib/theme.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
ThemeData get darkTheme => ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xAAbb71fc),
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
fontFamily: 'SourceSerif4',
|
||||
);
|
||||
@@ -3,28 +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) {
|
||||
final content = ContentProvider.getContent<String>(ContentType.text);
|
||||
return Text(content);
|
||||
} else if (contentType == ContentType.text ||
|
||||
contentType == ContentType.generalSkills) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(content),
|
||||
);
|
||||
}
|
||||
// List-based content-blocks
|
||||
List<dynamic> content =
|
||||
ContentProvider.getContent<List<dynamic>>(contentType);
|
||||
List<Widget> widgets = [];
|
||||
for (var item in content) {
|
||||
widgets.add(_buildListTile(item));
|
||||
|
||||
@@ -74,14 +74,22 @@ class ContentListTile extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
),
|
||||
Text(name),
|
||||
Text(', $location'),
|
||||
if (Breakpoints.xl < screenWidth) Text(' - $title'),
|
||||
if (Breakpoints.xl < screenWidth) const Text(' - '),
|
||||
if (Breakpoints.xl < screenWidth)
|
||||
Text(
|
||||
title,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Breakpoints.xl >= screenWidth) Text(title),
|
||||
if (Breakpoints.xl >= screenWidth)
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
titleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
@@ -89,13 +97,19 @@ class ContentListTile extends StatelessWidget {
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
Tools.buildTimeString(startDate, endDate),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
Tools.buildTimeString(startDate, endDate, context),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
trailing: Breakpoints.sm < screenWidth
|
||||
? Text(Tools.buildTimeString(startDate, endDate))
|
||||
? Text(
|
||||
Tools.buildTimeString(startDate, endDate, context),
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
@@ -108,14 +122,22 @@ class ContentListTile extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
),
|
||||
Text(name),
|
||||
Text(', $location'),
|
||||
if (Breakpoints.xl < screenWidth) Text(' - $title'),
|
||||
if (Breakpoints.xl < screenWidth) const Text(' - '),
|
||||
if (Breakpoints.xl < screenWidth)
|
||||
Text(
|
||||
title,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Breakpoints.xl >= screenWidth) Text(title),
|
||||
if (Breakpoints.xl >= screenWidth)
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
titleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
@@ -127,15 +149,21 @@ class ContentListTile extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
Tools.buildTimeString(startDate, endDate),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
Tools.buildTimeString(startDate, endDate, context),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(description),
|
||||
],
|
||||
),
|
||||
trailing: Breakpoints.sm < screenWidth
|
||||
? Text(Tools.buildTimeString(startDate, endDate))
|
||||
? Text(
|
||||
Tools.buildTimeString(startDate, endDate, context),
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
|
||||
52
lib/widgets/language_dropdown.dart
Normal file
52
lib/widgets/language_dropdown.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../providers/locale_provider.dart';
|
||||
|
||||
class LanguageDropdown extends StatefulWidget {
|
||||
const LanguageDropdown({super.key});
|
||||
|
||||
@override
|
||||
State<LanguageDropdown> createState() => _LanguageDropdownState();
|
||||
}
|
||||
|
||||
class _LanguageDropdownState extends State<LanguageDropdown> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
LocaleProvider localeProvider = Provider.of<LocaleProvider>(context);
|
||||
return DropdownButton<String>(
|
||||
value: localeProvider.locale.languageCode,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: 'de',
|
||||
child: getMenuItem(
|
||||
AppLocalizations.of(context)!.german,
|
||||
'assets/de_icon.png',
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 'en',
|
||||
child: getMenuItem(
|
||||
AppLocalizations.of(context)!.english,
|
||||
'assets/gb_icon.png',
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
localeProvider.setLocale(Locale(value));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget getMenuItem(String label, String imagePath) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(label),
|
||||
const Padding(padding: EdgeInsets.only(right: 8)),
|
||||
Image.asset(imagePath, width: 30),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class LanguageWidget extends StatelessWidget {
|
||||
const LanguageWidget({super.key});
|
||||
@@ -18,8 +19,9 @@ class LanguageWidget extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Deutsch', style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text('Muttersprache',
|
||||
Text(AppLocalizations.of(context)!.german,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text(AppLocalizations.of(context)!.mother_tongue,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
@@ -36,8 +38,10 @@ class LanguageWidget extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Englisch', style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text('Sehr gut', style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text(AppLocalizations.of(context)!.english,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text(AppLocalizations.of(context)!.very_good,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
23
pubspec.yaml
23
pubspec.yaml
@@ -12,6 +12,10 @@ dependencies:
|
||||
|
||||
cupertino_icons: ^1.0.8
|
||||
url_launcher: ^6.3.1
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
provider: ^6.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -20,10 +24,27 @@ dev_dependencies:
|
||||
flutter_lints: ^4.0.0
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- assets/content.json
|
||||
- assets/content/content_de.json
|
||||
- assets/content/content_en.json
|
||||
- assets/profile.jpg
|
||||
- assets/de_icon.png
|
||||
- assets/gb_icon.png
|
||||
|
||||
fonts:
|
||||
- family: Radley
|
||||
fonts:
|
||||
- asset: assets/fonts/radley/Radley-Regular.ttf
|
||||
style: normal
|
||||
- asset: assets/fonts/radley/Radley-Italic.ttf
|
||||
style: italic
|
||||
|
||||
- family: SourceSerif4
|
||||
fonts:
|
||||
- asset: assets/fonts/sourceserif4/SourceSerif4-VariableFont_opsz,wght.ttf
|
||||
style: normal
|
||||
- asset: assets/fonts/sourceserif4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf
|
||||
style: italic
|
||||
Reference in New Issue
Block a user