diff --git a/lib/constants.dart b/lib/constants.dart new file mode 100644 index 0000000..5513ab5 --- /dev/null +++ b/lib/constants.dart @@ -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 } diff --git a/lib/pages/landing_page.dart b/lib/pages/landing_page.dart index 077ae03..fed8e49 100644 --- a/lib/pages/landing_page.dart +++ b/lib/pages/landing_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.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: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 { const LandingPage({super.key}); @@ -40,25 +42,31 @@ class _LandingPageState extends State { (MediaQuery.of(context).size.width - _getMainContentWidth()) / 2; Widget _getSideBar() { - return ContentBox( - title: 'Fähigkeiten', - content: ContentProvider.skills, - contentType: ContentType.skills, + return const Column( + children: [ + ContentBlock( + blockTitle: 'Fähigkeiten', + contentType: ContentType.skills, + ), + Padding(padding: EdgeInsets.only(bottom: 25)), + ContentBlock( + blockTitle: 'Sprachen', + contentType: ContentType.language, + ), + ], ); } Widget _getMainContent() { - return Column( + return const Column( children: [ - ContentBox( - title: 'Arbeitserfahrung', - content: ContentProvider.experience, + ContentBlock( + blockTitle: 'Arbeitserfahrung', contentType: ContentType.experience, ), - const Padding(padding: EdgeInsets.only(bottom: 25)), - ContentBox( - title: 'Bildungsweg', - content: ContentProvider.education, + Padding(padding: EdgeInsets.only(bottom: 25)), + ContentBlock( + blockTitle: 'Bildungsweg', contentType: ContentType.education, ), ], @@ -74,7 +82,7 @@ class _LandingPageState extends State { child: SizedBox( width: _getSidebarWidth(), child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 50), + padding: EdgeInsets.symmetric(horizontal: 25), child: Profile(), ), ), @@ -84,7 +92,7 @@ class _LandingPageState extends State { child: SizedBox( width: _getSidebarWidth(), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 25), child: _getSideBar(), ), ), @@ -107,11 +115,12 @@ class _LandingPageState extends State { child: Column( children: [ const Padding( - padding: EdgeInsets.symmetric(horizontal: 50), + padding: EdgeInsets.symmetric(horizontal: 25), child: Profile(), ), + const Padding(padding: EdgeInsets.only(bottom: 25)), Padding( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 25), child: _getSideBar(), ), ], @@ -121,7 +130,7 @@ class _LandingPageState extends State { width: 900, child: _getMainContent(), ), - const Padding(padding: EdgeInsets.only(right: 50)), + const Padding(padding: EdgeInsets.only(right: 25)), ], ); } else if (constraints.maxWidth > Breakpoints.lg) { @@ -136,6 +145,7 @@ class _LandingPageState extends State { child: Column( children: [ const Profile(), + const Padding(padding: EdgeInsets.only(bottom: 25)), _getSideBar(), ], ), diff --git a/lib/services/content_provider.dart b/lib/services/content_provider.dart index 71c05a1..234f123 100644 --- a/lib/services/content_provider.dart +++ b/lib/services/content_provider.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/services.dart'; +import 'package:resume/constants.dart'; class ContentProvider { ContentProvider._(); @@ -17,11 +18,25 @@ class ContentProvider { return true; } - static Map _content = {}; + static Map _content = { + 'experience': >[], + 'education': >[], + 'skills': >[], + 'text': [], + }; - static List get experience => _content['experience']; - - static List get education => _content['education']; - - static List get skills => _content['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; + default: + return [] as T; + } + } } diff --git a/lib/services/tools.dart b/lib/services/tools.dart new file mode 100644 index 0000000..aa1a5c4 --- /dev/null +++ b/lib/services/tools.dart @@ -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}'; + } +} diff --git a/lib/widgets/content_block.dart b/lib/widgets/content_block.dart new file mode 100644 index 0000000..6239dd1 --- /dev/null +++ b/lib/widgets/content_block.dart @@ -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(ContentType.text); + return Text(content); + } + // List-based content-blocks + List content = + ContentProvider.getContent>(contentType); + List 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'], + ); + } + } +} diff --git a/lib/widgets/content_list_tile.dart b/lib/widgets/content_list_tile.dart index fb0a9cc..0f37818 100644 --- a/lib/widgets/content_list_tile.dart +++ b/lib/widgets/content_list_tile.dart @@ -1,90 +1,156 @@ 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( + const ContentListTile.education( {super.key, - this.name, - this.location, - this.title, - this.description, - this.startDate, - this.endDate}); + required this.name, + required this.location, + required this.title, + required this.startDate, + required this.endDate}) + : description = '', + percentage = '', + _contentType = ContentType.education; - final String? name; - final String? location; - final String? title; - final String? description; - final String? startDate; - final String? endDate; + 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: 5, + 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) Text(' - $title'), + ], + ), + if (Breakpoints.xl >= screenWidth) Text(title), + ], + ), + titleAlignment: ListTileTitleAlignment.titleHeight, + subtitle: Breakpoints.sm >= screenWidth + ? 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, + 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), + style: Theme.of(context).textTheme.labelSmall, + ), + ), + Text(description), + ], + ), + trailing: Breakpoints.sm < screenWidth + ? Text(Tools.buildTimeString(startDate, endDate)) + : null, + ); @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - return ListTile( - contentPadding: const EdgeInsets.all(0), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if (name != null) - Text( - name!, - ), - if (location != null) Text(', $location'), - if (title != null && Breakpoints.xl < width) Text(' - $title'), - ], - ), - if (title != null && Breakpoints.xl >= width) Text('$title'), - ], - ), - titleAlignment: ListTileTitleAlignment.titleHeight, - subtitle: description != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding(padding: EdgeInsets.only(top: 8)), - if (startDate != null && - endDate != null && - Breakpoints.sm >= width) - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Text( - _getTimeString(startDate!, endDate!), - style: Theme.of(context).textTheme.labelSmall, - ), - ), - Text(description!), - ], - ) - : null, - trailing: startDate != null && endDate != null && Breakpoints.sm < width - ? Text(_getTimeString(startDate!, endDate!)) - : null, - ); + 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(); + } } - - String _getTimeString(String startDate, String endDate) { - 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}'; - } - - static const months = [ - 'Januar', - 'Februar', - 'März', - 'April', - 'Mai', - 'Juni', - 'July', - 'August', - 'September', - 'Oktober', - 'November', - 'Dezember' - ]; } diff --git a/lib/widgets/content_widget.dart b/lib/widgets/content_widget.dart deleted file mode 100644 index ad07269..0000000 --- a/lib/widgets/content_widget.dart +++ /dev/null @@ -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 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 } diff --git a/lib/widgets/skill_list_tile.dart b/lib/widgets/skill_list_tile.dart deleted file mode 100644 index 0bd6070..0000000 --- a/lib/widgets/skill_list_tile.dart +++ /dev/null @@ -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, - ), - ), - ), - ], - ), - ); - } -}