Compare commits

..

3 Commits

Author SHA1 Message Date
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
12 changed files with 356 additions and 208 deletions

BIN
assets/de_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

BIN
assets/gb_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

17
lib/constants.dart Normal file
View File

@@ -0,0 +1,17 @@
/// 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 }

View File

@@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:resume/services/breakpoints.dart'; import 'package:resume/services/breakpoints.dart';
import 'package:resume/services/content_provider.dart';
import 'package:resume/widgets/content_widget.dart';
import 'package:resume/widgets/profile.dart'; import 'package:resume/widgets/profile.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:resume/constants.dart' show ContentType;
import '../services/content_provider.dart';
import '../widgets/content_block.dart';
class LandingPage extends StatefulWidget { class LandingPage extends StatefulWidget {
const LandingPage({super.key}); const LandingPage({super.key});
@@ -40,25 +42,31 @@ class _LandingPageState extends State<LandingPage> {
(MediaQuery.of(context).size.width - _getMainContentWidth()) / 2; (MediaQuery.of(context).size.width - _getMainContentWidth()) / 2;
Widget _getSideBar() { Widget _getSideBar() {
return ContentBox( return const Column(
title: 'Fähigkeiten', children: [
content: ContentProvider.skills, ContentBlock(
blockTitle: 'Fähigkeiten',
contentType: ContentType.skills, contentType: ContentType.skills,
),
Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBlock(
blockTitle: 'Sprachen',
contentType: ContentType.language,
),
],
); );
} }
Widget _getMainContent() { Widget _getMainContent() {
return Column( return const Column(
children: [ children: [
ContentBox( ContentBlock(
title: 'Arbeitserfahrung', blockTitle: 'Arbeitserfahrung',
content: ContentProvider.experience,
contentType: ContentType.experience, contentType: ContentType.experience,
), ),
const Padding(padding: EdgeInsets.only(bottom: 25)), Padding(padding: EdgeInsets.only(bottom: 25)),
ContentBox( ContentBlock(
title: 'Bildungsweg', blockTitle: 'Bildungsweg',
content: ContentProvider.education,
contentType: ContentType.education, contentType: ContentType.education,
), ),
], ],
@@ -74,7 +82,7 @@ class _LandingPageState extends State<LandingPage> {
child: SizedBox( child: SizedBox(
width: _getSidebarWidth(), width: _getSidebarWidth(),
child: const Padding( child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 50), padding: EdgeInsets.symmetric(horizontal: 25),
child: Profile(), child: Profile(),
), ),
), ),
@@ -84,7 +92,7 @@ class _LandingPageState extends State<LandingPage> {
child: SizedBox( child: SizedBox(
width: _getSidebarWidth(), width: _getSidebarWidth(),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 25),
child: _getSideBar(), child: _getSideBar(),
), ),
), ),
@@ -107,11 +115,12 @@ class _LandingPageState extends State<LandingPage> {
child: Column( child: Column(
children: [ children: [
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 50), padding: EdgeInsets.symmetric(horizontal: 25),
child: Profile(), child: Profile(),
), ),
const Padding(padding: EdgeInsets.only(bottom: 25)),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 25),
child: _getSideBar(), child: _getSideBar(),
), ),
], ],
@@ -121,7 +130,7 @@ class _LandingPageState extends State<LandingPage> {
width: 900, width: 900,
child: _getMainContent(), child: _getMainContent(),
), ),
const Padding(padding: EdgeInsets.only(right: 50)), const Padding(padding: EdgeInsets.only(right: 25)),
], ],
); );
} else if (constraints.maxWidth > Breakpoints.lg) { } else if (constraints.maxWidth > Breakpoints.lg) {
@@ -136,6 +145,7 @@ class _LandingPageState extends State<LandingPage> {
child: Column( child: Column(
children: [ children: [
const Profile(), const Profile(),
const Padding(padding: EdgeInsets.only(bottom: 25)),
_getSideBar(), _getSideBar(),
], ],
), ),

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:resume/constants.dart';
class ContentProvider { class ContentProvider {
ContentProvider._(); ContentProvider._();
@@ -17,11 +18,25 @@ class ContentProvider {
return true; return true;
} }
static Map<String, dynamic> _content = {}; static Map<String, dynamic> _content = {
'experience': <List<dynamic>>[],
'education': <List<dynamic>>[],
'skills': <List<dynamic>>[],
'text': <String>[],
};
static List<dynamic> get experience => _content['experience']; static T getContent<T>(ContentType contentType) {
switch (contentType) {
static List<dynamic> get education => _content['education']; case ContentType.experience:
return _content['experience'] as T;
static List<dynamic> get skills => _content['skills']; 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;
}
}
} }

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

@@ -0,0 +1,12 @@
import 'package:resume/constants.dart' show months;
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}';
}
}

View File

@@ -0,0 +1,83 @@
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;
import '../services/content_provider.dart';
class ContentBlock extends StatelessWidget {
const ContentBlock({
super.key,
required this.blockTitle,
required this.contentType,
});
final String blockTitle;
final ContentType contentType;
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);
}
// List-based content-blocks
List<dynamic> content =
ContentProvider.getContent<List<dynamic>>(contentType);
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

@@ -1,28 +1,72 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:resume/services/breakpoints.dart'; import 'package:resume/services/breakpoints.dart';
import 'package:resume/constants.dart' show ContentType;
import '../services/tools.dart';
class ContentListTile extends StatelessWidget { class ContentListTile extends StatelessWidget {
const ContentListTile( const ContentListTile.education(
{super.key, {super.key,
this.name, required this.name,
this.location, required this.location,
this.title, required this.title,
this.description, required this.startDate,
this.startDate, required this.endDate})
this.endDate}); : description = '',
percentage = '',
_contentType = ContentType.education;
final String? name; const ContentListTile.experience(
final String? location; {super.key,
final String? title; required this.name,
final String? description; required this.location,
final String? startDate; required this.title,
final String? endDate; required this.description,
required this.startDate,
required this.endDate})
: percentage = '',
_contentType = ContentType.experience;
@override const ContentListTile.skills(
Widget build(BuildContext context) { {super.key, required this.name, required this.percentage})
final width = MediaQuery.of(context).size.width; : description = '',
title = ',',
location = '',
startDate = '',
endDate = '',
_contentType = ContentType.skills;
return ListTile( 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), contentPadding: const EdgeInsets.all(0),
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -30,61 +74,83 @@ class ContentListTile extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
if (name != null)
Text( Text(
name!, name,
), ),
if (location != null) Text(', $location'), Text(', $location'),
if (title != null && Breakpoints.xl < width) Text(' - $title'), if (Breakpoints.xl < screenWidth) Text(' - $title'),
], ],
), ),
if (title != null && Breakpoints.xl >= width) Text('$title'), if (Breakpoints.xl >= screenWidth) Text(title),
], ],
), ),
titleAlignment: ListTileTitleAlignment.titleHeight, titleAlignment: ListTileTitleAlignment.titleHeight,
subtitle: description != null subtitle: Breakpoints.sm >= screenWidth
? Column( ? Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
Tools.buildTimeString(startDate, endDate),
style: Theme.of(context).textTheme.labelSmall,
),
)
: null,
trailing: Breakpoints.sm < screenWidth
? Text(Tools.buildTimeString(startDate, endDate))
: 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) Text(' - $title'),
],
),
if (Breakpoints.xl >= screenWidth) Text(title),
],
),
titleAlignment: ListTileTitleAlignment.titleHeight,
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Padding(padding: EdgeInsets.only(top: 8)), const Padding(padding: EdgeInsets.only(top: 8)),
if (startDate != null && if (Breakpoints.sm >= screenWidth)
endDate != null &&
Breakpoints.sm >= width)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: Text( child: Text(
_getTimeString(startDate!, endDate!), Tools.buildTimeString(startDate, endDate),
style: Theme.of(context).textTheme.labelSmall, style: Theme.of(context).textTheme.labelSmall,
), ),
), ),
Text(description!), Text(description),
], ],
) ),
: null, trailing: Breakpoints.sm < screenWidth
trailing: startDate != null && endDate != null && Breakpoints.sm < width ? Text(Tools.buildTimeString(startDate, endDate))
? Text(_getTimeString(startDate!, endDate!))
: null, : null,
); );
}
String _getTimeString(String startDate, String endDate) { @override
final firstDate = DateTime.parse('$startDate-01'); Widget build(BuildContext context) {
final secondDate = DateTime.parse('$endDate-01'); final width = MediaQuery.of(context).size.width;
return '${months[firstDate.month - 1]} ${firstDate.year} - ${months[secondDate.month - 1]} ${secondDate.year}';
}
static const months = [ if (_contentType == ContentType.skills) {
'Januar', return _skillsListTile;
'Februar', } else if (_contentType == ContentType.education) {
'März', return _getEducationListTile(context, width);
'April', } else if (_contentType == ContentType.experience) {
'Mai', return _getExperienceListTile(context, width);
'Juni', } else {
'July', return const Placeholder();
'August', }
'September', }
'Oktober',
'November',
'Dezember'
];
} }

View File

@@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
import 'package:resume/widgets/content_list_tile.dart';
import 'package:resume/widgets/skill_list_tile.dart';
class ContentBox extends StatelessWidget {
const ContentBox({
super.key,
required this.title,
required this.content,
required this.contentType,
});
final ContentType contentType;
final List<dynamic> content;
final String title;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineMedium,
),
const Padding(padding: EdgeInsets.only(bottom: 8)),
ListView.builder(
shrinkWrap: true,
itemCount: content.length,
itemBuilder: (context, index) => _buildListTile(content[index]),
),
],
),
),
);
}
Widget _buildListTile(Map data) {
switch (contentType) {
case ContentType.experience:
return ContentListTile(
name: data['name'],
location: data['location'],
title: data['title'],
description: data['description'],
startDate: data['startDate'],
endDate: data['endDate'],
);
case ContentType.education:
return ContentListTile(
name: data['name'],
location: data['location'],
title: data['title'],
startDate: data['startDate'],
endDate: data['endDate'],
);
case ContentType.skills:
return SkillListTile(
name: data['name'],
percentage: data['percentage'],
);
}
}
}
enum ContentType { experience, education, skills }

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.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('Deutsch', style: Theme.of(context).textTheme.bodyLarge),
Text('Muttersprache',
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('Englisch', style: Theme.of(context).textTheme.bodyLarge),
Text('Sehr gut', style: Theme.of(context).textTheme.bodyMedium),
],
),
],
),
],
);
}
}

View File

@@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
class SkillListTile extends StatelessWidget {
const SkillListTile({
super.key,
required this.name,
this.percentage,
});
final String name;
final String? percentage;
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: const EdgeInsets.all(0),
title: Row(
children: [
Expanded(flex: 2, child: Text(name)),
const Padding(padding: EdgeInsets.only(bottom: 8)),
if (percentage != null)
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: LinearProgressIndicator(
value: double.parse(percentage!) / 100,
),
),
),
],
),
);
}
}

View File

@@ -25,3 +25,5 @@ flutter:
assets: assets:
- assets/content.json - assets/content.json
- assets/profile.jpg - assets/profile.jpg
- assets/de_icon.png
- assets/gb_icon.png