Compare commits

...

34 Commits

Author SHA1 Message Date
b66da23887 depending on the path a different language is pre-selected 2024-12-18 19:41:00 +01:00
6587828a0b content localized on locale change 2024-12-18 19:30:13 +01:00
a984fc15b0 added localizations for content 2024-12-18 18:41:37 +01:00
0d9118ab3e fixed typo 2024-12-18 18:22:14 +01:00
85b5d4dc7c updated gitignore and changed content directory 2024-12-18 18:21:37 +01:00
ebf1246a55 added month localizations 2024-12-18 18:17:22 +01:00
8e91388ecf Localized static text 2024-12-18 18:05:14 +01:00
b9033014c8 added more localized terms 2024-12-18 18:01:41 +01:00
0a877525aa added localization 2024-12-18 17:56:33 +01:00
ff97898b90 simple language dropdown without function 2024-12-18 17:36:24 +01:00
c523b7495f Fixed content not being responsive 2024-12-18 17:16:39 +01:00
4c2162a158 added additional skills 2024-12-05 20:45:00 +01:00
ee705938f7 Changed Font and Theme 2024-12-05 20:26:34 +01:00
35e1171623 Changed colors 2024-12-05 20:06:18 +01:00
6ebfa77416 Added "About me"-Section 2024-12-05 16:19:45 +01:00
b8840ffe0c Changed Title of LandingPage 2024-12-05 15:57:46 +01:00
c62dbbb707 Fixed bug that caused the skills list to display 100% every time 2024-12-05 15:56:46 +01:00
7fac0160e0 Refactoring 2024-12-05 15:54:29 +01:00
3ba6f9d714 Language-Widget added 2024-12-05 15:54:23 +01:00
683f0174ef Disallowed indexing 2024-12-03 01:15:57 +01:00
fc1823aec0 Refactoring 2024-12-03 01:06:58 +01:00
634ecce4d9 Implemented breaking points for varios screen sizes 2024-12-03 00:54:49 +01:00
69675f42e2 added url_launcher to link to code repository 2024-12-02 19:32:14 +01:00
da4a057fd7 Changed font size 2024-12-02 19:23:22 +01:00
cf79defe49 added simple profile 2024-12-02 19:18:15 +01:00
9982c3396a initial start page 2024-12-02 19:05:36 +01:00
e22fe2e58f changed title style 2024-12-02 18:29:28 +01:00
f91c8100be added breakpoints 2024-12-02 18:29:15 +01:00
7f9374a768 fixed type issue 2024-12-02 18:21:26 +01:00
7bb7037618 added start and end date 2024-12-02 18:21:17 +01:00
06da803497 Minor alignment changes 2024-12-02 17:55:08 +01:00
7a1bbfe365 created content boxes 2024-12-02 17:50:31 +01:00
d715b4b201 Added ContentProvider 2024-12-02 16:39:34 +01:00
bacf168d09 added content.json 2024-12-02 16:38:32 +01:00
28 changed files with 1017 additions and 82 deletions

20
.gitignore vendored
View File

@@ -46,6 +46,23 @@ analysis_benchmark.json
# packages file containing multi-root paths # packages file containing multi-root paths
.packages.generated .packages.generated
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
/dev/docs/api_docs.zip
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
@@ -137,3 +154,6 @@ app.*.symbols
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock !/dev/ci/**/Gemfile.lock
!.vscode/settings.json !.vscode/settings.json
# Custom
content/

BIN
assets/de_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

Binary file not shown.

BIN
assets/gb_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
assets/profile.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

3
devtools_options.yaml Normal file
View 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
View File

@@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

8
lib/constants.dart Normal file
View File

@@ -0,0 +1,8 @@
enum ContentType {
experience,
education,
skills,
language,
text,
generalSkills
}

28
lib/l10n/app_de.arb Normal file
View 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
View 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"
}

View File

@@ -1,26 +1,60 @@
import 'package:flutter/material.dart'; 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: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() { 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}); const Resume({super.key});
@override
State<Resume> createState() => _ResumeState();
}
class _ResumeState extends State<Resume> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return Consumer<LocaleProvider>(
builder: (context, localeProvider, child) => MaterialApp(
title: 'Resume', title: 'Resume',
theme: ThemeData( theme: darkTheme,
colorScheme: ColorScheme.fromSeed( localizationsDelegates: const [
seedColor: Colors.deepPurple, brightness: Brightness.dark), AppLocalizations.delegate,
useMaterial3: true, GlobalMaterialLocalizations.delegate,
), GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
],
locale: localeProvider.locale,
routes: { routes: {
'/': (context) => const LandingPage(), '/': (context) => const LandingPage(),
}, },
initialRoute: '/', initialRoute: '/',
),
); );
} }
} }

View File

@@ -1,18 +1,251 @@
import 'package:flutter/material.dart'; 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';
class LandingPage extends StatelessWidget { import '../providers/content_provider.dart';
import '../widgets/content_block.dart';
class LandingPage extends StatefulWidget {
const LandingPage({super.key}); const LandingPage({super.key});
static const String routeName = '/'; static const String routeName = '/';
@override @override
Widget build(BuildContext context) { State<LandingPage> createState() => _LandingPageState();
return Scaffold( }
appBar: AppBar(
title: const Text('Landing'), class _LandingPageState extends State<LandingPage> {
actions: [ bool loadingDone = false;
TextButton(onPressed: () {}, child: const Text('Source Code')), late ContentProvider contentProvider;
late Locale currentLocale;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Provider.of<ContentProvider>(context, listen: false)
.loadContent(context);
setState(() => loadingDone = true);
});
currentLocale = context.read<LocaleProvider>().locale;
}
double _getMainContentWidth() {
final width = MediaQuery.of(context).size.width;
// if (width < Breakpoints.sm) return width;
if (width < Breakpoints.md) return width * 0.95;
if (width < Breakpoints.lg) return width * 0.8;
if (width < Breakpoints.xl) return width * 0.7;
if (width < Breakpoints.xl2) return width * 0.6;
return width * 0.5;
}
double _getSidebarWidth() =>
(MediaQuery.of(context).size.width - _getMainContentWidth()) / 2;
Widget _getSideBar() {
return Column(
children: [
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),
),
],
);
}
Widget _getMainContent() {
return Column(
children: [
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),
),
],
);
}
Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > Breakpoints.xl2) {
return Stack(
children: [
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: _getSidebarWidth(),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 25),
child: Profile(),
),
),
),
Align(
alignment: Alignment.topRight,
child: SizedBox(
width: _getSidebarWidth(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: _getSideBar(),
),
),
),
Center(
child: SizedBox(
width: _getMainContentWidth(),
child: _getMainContent(),
),
),
],
);
} else if (constraints.maxWidth > Breakpoints.xl) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 25),
child: Profile(),
),
const Padding(padding: EdgeInsets.only(bottom: 25)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: _getSideBar(),
),
],
),
),
SizedBox(
width: 900,
child: _getMainContent(),
),
const Padding(padding: EdgeInsets.only(right: 25)),
],
);
} else if (constraints.maxWidth > Breakpoints.lg) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
const Profile(),
const Padding(padding: EdgeInsets.only(bottom: 25)),
_getSideBar(),
],
),
),
),
SizedBox(
width: 650,
child: _getMainContent(),
),
const Padding(padding: EdgeInsets.only(right: 25)),
],
);
} else {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
const Profile(),
const Padding(padding: EdgeInsets.only(bottom: 25)),
_getMainContent(),
const Padding(padding: EdgeInsets.only(bottom: 25)),
_getSideBar(),
], ],
), ),
); );
} }
}
@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: Text(AppLocalizations.of(context)!.resume),
actions: [
TextButton(
onPressed: _launchURL,
child: Text(AppLocalizations.of(context)!.source_code)),
const LanguageDropdown(),
],
),
body: !loadingDone
? const Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
Text('Loading...')
],
),
)
: SingleChildScrollView(
child: LayoutBuilder(
builder: _buildLayout,
),
),
);
}
}
void _launchURL() async {
final Uri url = Uri.parse('https://git.skup.in/marco/resume');
await launchUrl(url);
} }

View 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;
}
}
}

View 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();
}
}

View File

@@ -0,0 +1,7 @@
class Breakpoints {
static const sm = 640;
static const md = 768;
static const lg = 1024;
static const xl = 1280;
static const xl2 = 1536;
}

108
lib/services/tools.dart Normal file
View File

@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class Tools {
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
View 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',
);

View File

@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:resume/widgets/content_list_tile.dart';
import 'package:resume/widgets/language_widget.dart';
import 'package:resume/constants.dart' show ContentType;
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) {
return SizedBox(
width: double.infinity,
child: Text(content),
);
}
// List-based content-blocks
List<Widget> widgets = [];
for (var item in content) {
widgets.add(_buildListTile(item));
}
return Column(
children: widgets,
);
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
blockTitle,
style: Theme.of(context).textTheme.headlineMedium,
),
const Padding(padding: EdgeInsets.only(bottom: 8)),
_getContentWidget,
],
),
),
);
}
Widget _buildListTile(Map data) {
if (contentType == ContentType.experience) {
return ContentListTile.experience(
name: data['name'],
location: data['location'],
title: data['title'],
description: data['description'],
startDate: data['startDate'],
endDate: data['endDate'],
);
} else if (contentType == ContentType.education) {
return ContentListTile.education(
name: data['name'],
location: data['location'],
title: data['title'],
startDate: data['startDate'],
endDate: data['endDate'],
);
} else {
return ContentListTile.skills(
name: data['name'],
percentage: data['percentage'],
);
}
}
}

View File

@@ -0,0 +1,184 @@
import 'package:flutter/material.dart';
import 'package:resume/services/breakpoints.dart';
import 'package:resume/constants.dart' show ContentType;
import '../services/tools.dart';
class ContentListTile extends StatelessWidget {
const ContentListTile.education(
{super.key,
required this.name,
required this.location,
required this.title,
required this.startDate,
required this.endDate})
: description = '',
percentage = '',
_contentType = ContentType.education;
const ContentListTile.experience(
{super.key,
required this.name,
required this.location,
required this.title,
required this.description,
required this.startDate,
required this.endDate})
: percentage = '',
_contentType = ContentType.experience;
const ContentListTile.skills(
{super.key, required this.name, required this.percentage})
: description = '',
title = ',',
location = '',
startDate = '',
endDate = '',
_contentType = ContentType.skills;
final String description;
final String endDate;
final String location;
final String name;
final String percentage;
final String startDate;
final String title;
final ContentType _contentType;
Widget get _skillsListTile => ListTile(
contentPadding: const EdgeInsets.all(0),
title: Row(
children: [
Expanded(flex: 2, child: Text(name)),
const Padding(padding: EdgeInsets.only(bottom: 8)),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: LinearProgressIndicator(
value: (double.tryParse(percentage) ?? 1) / 100,
),
),
),
],
),
);
Widget _getEducationListTile(BuildContext context, double screenWidth) =>
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(name),
Text(', $location'),
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,
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
],
),
titleAlignment: ListTileTitleAlignment.titleHeight,
subtitle: Breakpoints.sm >= screenWidth
? Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
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, context),
style:
TextStyle(color: Theme.of(context).colorScheme.secondary),
)
: null,
);
Widget _getExperienceListTile(BuildContext context, double screenWidth) =>
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(name),
Text(', $location'),
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,
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
],
),
titleAlignment: ListTileTitleAlignment.titleHeight,
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(padding: EdgeInsets.only(top: 8)),
if (Breakpoints.sm >= screenWidth)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
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, context),
style:
TextStyle(color: Theme.of(context).colorScheme.secondary),
)
: null,
);
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (_contentType == ContentType.skills) {
return _skillsListTile;
} else if (_contentType == ContentType.education) {
return _getEducationListTile(context, width);
} else if (_contentType == ContentType.experience) {
return _getExperienceListTile(context, width);
} else {
return const Placeholder();
}
}
}

View 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),
],
);
}
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LanguageWidget extends StatelessWidget {
const LanguageWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(
'assets/de_icon.png',
width: 50,
),
const Padding(padding: EdgeInsets.only(right: 15)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.german,
style: Theme.of(context).textTheme.bodyLarge),
Text(AppLocalizations.of(context)!.mother_tongue,
style: Theme.of(context).textTheme.bodyMedium),
],
),
],
),
const Padding(padding: EdgeInsets.only(bottom: 8)),
Row(
children: [
Image.asset(
'assets/gb_icon.png',
width: 50,
),
const Padding(padding: EdgeInsets.only(right: 15)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.english,
style: Theme.of(context).textTheme.bodyLarge),
Text(AppLocalizations.of(context)!.very_good,
style: Theme.of(context).textTheme.bodyMedium),
],
),
],
),
],
);
}
}

46
lib/widgets/profile.dart Normal file
View File

@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
class Profile extends StatelessWidget {
const Profile({super.key});
@override
Widget build(BuildContext context) {
return Card(
child: SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Container(
constraints: const BoxConstraints(maxWidth: 200),
child: ClipRRect(
borderRadius: BorderRadius.circular(180),
child: Image.asset('assets/profile.jpg'),
),
),
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Text(
'Marco Skupin',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayMedium,
),
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Text(
'Master of Science',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge,
),
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
Text(
'marco@skup.in',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
}
}

View File

@@ -1,90 +1,50 @@
name: resume name: resume
description: "A new Flutter project." description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
url_launcher: ^6.3.1
flutter_localizations:
sdk: flutter
intl: any
provider: ^6.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter: flutter:
generate: true
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: assets:
# assets: - assets/content/content_de.json
# - images/a_dot_burr.jpeg - assets/content/content_en.json
# - images/a_dot_ham.jpeg - assets/profile.jpg
- assets/de_icon.png
- assets/gb_icon.png
# An image asset can refer to one or more resolution-specific "variants", see fonts:
# https://flutter.dev/to/resolution-aware-images - family: Radley
fonts:
- asset: assets/fonts/radley/Radley-Regular.ttf
style: normal
- asset: assets/fonts/radley/Radley-Italic.ttf
style: italic
# For details regarding adding assets from package dependencies, see - family: SourceSerif4
# https://flutter.dev/to/asset-from-package fonts:
- asset: assets/fonts/sourceserif4/SourceSerif4-VariableFont_opsz,wght.ttf
# To add custom fonts to your application, add a fonts section here, style: normal
# in this "flutter" section. Each entry in this list should have a - asset: assets/fonts/sourceserif4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf
# "family" key with the font family name, and a "fonts" key with a style: italic
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta name="robots" content="noindex">
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.

2
web/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /