Created
June 8, 2025 06:21
-
-
Save ben953/e3d24ee6b64276fc9ef5c872348f5a4f to your computer and use it in GitHub Desktop.
Sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:google_fonts/google_fonts.dart'; | |
final bool includeHeadshot = true; | |
typedef EducationEntry = ({ | |
String? degree, | |
String? dates, | |
String? school, | |
String? description, | |
}); | |
typedef WorkExperienceEntry = ({ | |
String? company, | |
String? location, | |
String? dates, | |
String? position, | |
String? description, // Assumed to be a multi-line string for bullet points | |
}); | |
typedef ContactInfo = ({ | |
String? phone, | |
String? address2, | |
String? address1, | |
String? lastName, | |
String? firstName, | |
String? email, | |
String? website, | |
List<({String? type, String? url})> socialLinks, | |
}); | |
typedef SimpleEntry = ({String? itemTitle, String? itemDescription}); | |
typedef AdditionalSectionEntry = ({ | |
String? sectionTitle, | |
List<SimpleEntry> content, | |
}); | |
typedef AdditionalExperienceItemEntry = ({ | |
String? title, | |
String? subtitle, | |
String? location, | |
String? dates, | |
String? description, | |
}); | |
typedef AdditionalExperienceSectionEntry = ({ | |
String? sectionTitle, | |
List<AdditionalExperienceItemEntry> content, | |
}); | |
class ResumeData { | |
final String? professionalHeadline; | |
final String? professionalSummary; | |
final List<EducationEntry> education; | |
final List<WorkExperienceEntry> work; | |
final ContactInfo contact; | |
final List<SimpleEntry> skills; | |
final List<AdditionalSectionEntry> additionalSimpleSections; | |
final List<AdditionalExperienceSectionEntry> additionalExperiences; | |
ResumeData({ | |
required this.professionalHeadline, | |
required this.professionalSummary, | |
required this.education, | |
required this.work, | |
required this.contact, | |
required this.skills, | |
required this.additionalSimpleSections, | |
required this.additionalExperiences, | |
}); | |
} | |
final sample = ResumeData( | |
professionalHeadline: 'Senior Software Engineer & Cloud Architect', | |
professionalSummary: | |
'Innovative senior software engineer with 8+ years of experience building scalable distributed systems and cloud-native applications. Expert in microservices architecture, cloud platforms (AWS/GCP), and machine learning engineering. Strong track record of leading engineering teams, mentoring developers, and delivering high-impact projects. Passionate about solving complex technical challenges and driving engineering excellence.', | |
education: [ | |
( | |
degree: 'Ph.D. in Computer Science', | |
dates: '2022 - Present', | |
school: 'Stanford University', | |
description: | |
'Research focus on distributed systems and cloud computing. Published 3 papers in top-tier conferences. Teaching assistant for Advanced Algorithms course.', | |
), | |
( | |
degree: 'M.S. Computer Science', | |
dates: '2020 - 2022', | |
school: 'University of Washington', | |
description: | |
'Specialized in machine learning and artificial intelligence. Completed thesis on neural network optimization techniques.', | |
), | |
( | |
degree: 'B.S. Computer Science', | |
dates: '2016 - 2020', | |
school: 'University of Michigan', | |
description: | |
'Graduated summa cum laude with 3.95 GPA. Led undergraduate research project on parallel computing algorithms.', | |
), | |
], | |
work: [ | |
( | |
description: | |
'Lead architect for cloud-native microservices platform serving millions of users. Designed and implemented scalable distributed systems using Kubernetes and AWS. Reduced infrastructure costs by 40% through optimization.\n\nManaged team of 8 engineers across 3 time zones. Established agile development practices and CI/CD pipelines that increased deployment frequency by 300%. Mentored junior developers and conducted technical interviews.', | |
company: 'Amazon Web Services', | |
location: 'Seattle, WA', | |
dates: '2022 - Present', | |
position: 'Senior Software Engineer', | |
), | |
( | |
description: | |
'Developed machine learning models for fraud detection that reduced fraudulent transactions by 60%. Built real-time data processing pipeline handling 10K+ events per second.\n\nLed migration from monolithic to microservices architecture, improving system reliability and reducing deployment time by 75%. Implemented comprehensive monitoring and alerting using Prometheus and Grafana.', | |
company: 'Stripe', | |
location: 'San Francisco, CA', | |
dates: '2020 - 2022', | |
position: 'Software Engineer', | |
), | |
( | |
description: | |
'Created React/Node.js web applications for enterprise clients. Implemented responsive UI components and RESTful APIs. Optimized database queries resulting in 40% performance improvement.\n\nCollaborated with product and design teams to deliver features on schedule. Conducted code reviews and maintained documentation. Mentored 2 junior developers.', | |
company: 'Microsoft', | |
location: 'Redmond, WA', | |
dates: '2018 - 2020', | |
position: 'Software Engineer', | |
), | |
( | |
description: | |
'Built full-stack web applications using Python/Django and React. Implemented automated testing suite that achieved 90% code coverage. Optimized front-end performance reducing load times by 50%.', | |
company: 'Tech Solutions Inc.', | |
location: 'Ann Arbor, MI', | |
dates: '2016 - 2018', | |
position: 'Software Engineer', | |
), | |
], | |
contact: ( | |
phone: '555-123-4567', | |
address2: 'Apt 4B', | |
address1: '123 Main St', | |
lastName: 'Doe', | |
firstName: 'John', | |
email: 'john.doe@example.com', | |
website: 'johndoe.dev', | |
socialLinks: [ | |
(type: 'LinkedIn', url: 'linkedin.com/in/johndoe'), | |
(type: 'GitHub', url: 'github.com/johndoe'), | |
], | |
), | |
skills: [ | |
( | |
itemTitle: 'Cloud Platforms', | |
itemDescription: 'AWS, Google Cloud Platform, Azure', | |
), | |
( | |
itemTitle: 'Programming Languages', | |
itemDescription: 'Python, Java, JavaScript, Go, C++', | |
), | |
( | |
itemTitle: 'Web Technologies', | |
itemDescription: 'React, Node.js, Django, GraphQL', | |
), | |
( | |
itemTitle: 'DevOps & Infrastructure', | |
itemDescription: 'Kubernetes, Docker, Terraform, Jenkins', | |
), | |
( | |
itemTitle: 'Databases', | |
itemDescription: 'PostgreSQL, MongoDB, Redis, Elasticsearch', | |
), | |
( | |
itemTitle: 'Machine Learning', | |
itemDescription: 'TensorFlow, PyTorch, Scikit-learn', | |
), | |
( | |
itemTitle: 'System Design', | |
itemDescription: 'Microservices, Distributed Systems', | |
), | |
(itemTitle: 'Development Practices', itemDescription: 'Agile, CI/CD, TDD'), | |
( | |
itemTitle: 'Team Leadership', | |
itemDescription: 'Technical Leadership, Mentoring', | |
), | |
( | |
itemTitle: 'Problem Solving', | |
itemDescription: 'Algorithm Design, Performance Optimization', | |
), | |
], | |
additionalSimpleSections: [ | |
( | |
sectionTitle: 'Awards & Recognition', | |
content: [ | |
(itemTitle: 'Dean\'s List', itemDescription: 'Spring 2021, Fall 2021'), | |
(itemTitle: 'Innovation Award', itemDescription: null), | |
( | |
itemTitle: 'Best Technical Solution', | |
itemDescription: 'Company Hackathon 2022', | |
), | |
( | |
itemTitle: 'Distinguished Engineer', | |
itemDescription: 'Microsoft 2019', | |
), | |
( | |
itemTitle: 'Top Contributor', | |
itemDescription: 'Open Source Community 2021', | |
), | |
], | |
), | |
( | |
sectionTitle: 'Publications', | |
content: [ | |
( | |
itemTitle: 'Scalable Machine Learning Systems', | |
itemDescription: 'IEEE Conference 2022', | |
), | |
( | |
itemTitle: 'Microservices Architecture Patterns', | |
itemDescription: 'ACM Journal 2021', | |
), | |
], | |
), | |
( | |
sectionTitle: 'Certifications', | |
content: [ | |
( | |
itemTitle: 'AWS Solutions Architect Professional', | |
itemDescription: '2022', | |
), | |
( | |
itemTitle: 'Google Cloud Professional Architect', | |
itemDescription: '2021', | |
), | |
(itemTitle: 'Kubernetes Administrator (CKA)', itemDescription: '2020'), | |
], | |
), | |
], | |
additionalExperiences: [ | |
( | |
sectionTitle: 'Projects', | |
content: [ | |
( | |
title: 'Distributed Machine Learning Platform', | |
subtitle: 'Open Source Project', | |
location: 'GitHub', | |
dates: '2021 - Present', | |
description: | |
'Created open-source platform for distributed ML training. 1000+ GitHub stars, 50+ contributors.', | |
), | |
( | |
title: 'Real-time Analytics Engine', | |
subtitle: 'Personal Project', | |
location: 'Seattle, WA', | |
dates: '2020', | |
description: | |
'Built scalable analytics engine processing 1M+ events/day using Kafka and Spark.', | |
), | |
], | |
), | |
], | |
); | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: Scaffold( | |
body: MyResume( | |
resumeData: sample, | |
pageMargin: 100, | |
professionalHeadshot: includeHeadshot | |
? Container(width: 100, height: 100, color: Colors.blue) | |
: null, | |
), | |
), | |
); | |
} | |
} | |
class CustomText extends StatelessWidget { | |
final String text; | |
final TextStyle style; | |
final TextAlign textAlign; | |
final bool bulletedList; | |
const CustomText( | |
this.text, { | |
super.key, | |
required this.style, | |
this.bulletedList = false, | |
this.textAlign = TextAlign.left, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Text(text, style: style, textAlign: textAlign); | |
} | |
} | |
class CustomTextWithDescription extends StatelessWidget { | |
final String? itemTitle; | |
final String? itemDescription; | |
final TextStyle? itemTitleStyle; | |
final TextStyle? itemDescriptionStyle; | |
final bool showBullet; | |
final bool oneLine; | |
final TextAlign textAlign; | |
const CustomTextWithDescription({ | |
super.key, | |
this.itemTitle, | |
this.itemDescription, | |
this.itemTitleStyle, | |
this.itemDescriptionStyle, | |
this.showBullet = false, | |
this.oneLine = false, | |
this.textAlign = TextAlign.left, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (itemTitle != null) | |
Text(itemTitle!, style: itemTitleStyle, textAlign: textAlign), | |
if (itemDescription != null) | |
Text( | |
itemDescription!, | |
style: itemDescriptionStyle, | |
textAlign: textAlign, | |
), | |
], | |
); | |
} | |
} | |
final Map<String, String> myResumeContent = { | |
'headerGreeting': 'Hello!!\nI\'m', | |
'profileTitle': 'My Profile.', | |
'addressLabel': 'Address', | |
'phoneLabel': 'Phone', | |
'emailLabel': 'Email', | |
'websiteLabel': 'Website', | |
'educationTitle': 'My Education.', | |
'skillsTitle': 'Professional Skill.', | |
'experienceTitle': 'My Experience.', | |
}; | |
final Map<String, Color> myResumeColors = { | |
'accentColor': Color(0xFFF3B85A), | |
'backgroundColor': Color(0xFFF1F1F1), | |
'primaryTextColor': Color(0xFF333333), | |
'secondaryTextColor': Color(0xFF555555), | |
'whiteColor': Color(0xFFFFFFFF), | |
}; | |
final Map<String, TextStyle> myResumeTextStyles = { | |
'headerNameStyle': GoogleFonts.playfairDisplay( | |
fontSize: 54, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'headerHelloStyle': GoogleFonts.playfairDisplay( | |
fontSize: 32, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'professionalTitleStyle': GoogleFonts.montserrat( | |
color: Color(0xFF555555), | |
letterSpacing: 3.0, | |
fontSize: 12, | |
fontWeight: FontWeight.w600, | |
), | |
'sectionTitleStyle': GoogleFonts.montserrat( | |
fontSize: 20, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'jobPositionStyle': GoogleFonts.montserrat( | |
fontSize: 16, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'itemTitleStyle': GoogleFonts.montserrat( | |
fontSize: 14, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'contactLabelStyle': GoogleFonts.montserrat( | |
fontSize: 14, | |
fontWeight: FontWeight.bold, | |
color: Color(0xFF333333), | |
), | |
'bodyTextStyle': GoogleFonts.lato( | |
fontSize: 13, | |
color: Color(0xFF555555), | |
height: 1.6, | |
), | |
'datesAndCompanyStyle': GoogleFonts.lato( | |
fontSize: 13, | |
color: Color(0xFF555555), | |
), | |
}; | |
class MyResume extends StatelessWidget { | |
final ResumeData resumeData; | |
final double pageMargin; | |
final Widget? professionalHeadshot; | |
static final Color accentColor = myResumeColors['accentColor']!; | |
static final Color backgroundColor = myResumeColors['backgroundColor']!; | |
static final Color primaryTextColor = myResumeColors['primaryTextColor']!; | |
static final Color secondaryTextColor = myResumeColors['secondaryTextColor']!; | |
static final Color whiteColor = myResumeColors['whiteColor']!; | |
static final TextStyle headerNameStyle = myResumeTextStyles['headerNameStyle']!; | |
static final TextStyle headerHelloStyle = myResumeTextStyles['headerHelloStyle']!; | |
static final TextStyle professionalTitleStyle = myResumeTextStyles['professionalTitleStyle']!; | |
static final TextStyle sectionTitleStyle = myResumeTextStyles['sectionTitleStyle']!; | |
static final TextStyle jobPositionStyle = myResumeTextStyles['jobPositionStyle']!; | |
static final TextStyle itemTitleStyle = myResumeTextStyles['itemTitleStyle']!; | |
static final TextStyle contactLabelStyle = myResumeTextStyles['contactLabelStyle']!; | |
static final TextStyle bodyTextStyle = myResumeTextStyles['bodyTextStyle']!; | |
static final TextStyle datesAndCompanyStyle = myResumeTextStyles['datesAndCompanyStyle']!; | |
static final double _kHeaderInternalPadding = 40.0; | |
static final double _kBodyInternalTopPadding = 30.0; | |
static final double _kSectionSpacing = 40.0; | |
static final double _kInterColumnSpacing = 50.0; | |
static final double _kSubSectionSpacing = 20.0; | |
static final double _kExperienceItemBottomMargin = 24.0; | |
static final double _kItemSpacing = 16.0; | |
static final double _kSubItemSpacing = 8.0; | |
static final double _kTinySpacing = 4.0; | |
MyResume({ | |
super.key, | |
required this.resumeData, | |
required this.pageMargin, | |
this.professionalHeadshot, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
final fullName = [resumeData.contact.firstName, resumeData.contact.lastName] | |
.where((s) => s != null && s.isNotEmpty) | |
.join(' '); | |
return Column( | |
children: [ | |
_buildHeader(fullName), | |
_buildBody(), | |
], | |
); | |
} | |
Widget _buildHeader(String fullName) { | |
if (professionalHeadshot != null) { | |
return Container( | |
color: whiteColor, | |
padding: EdgeInsets.fromLTRB( | |
pageMargin, pageMargin, pageMargin, _kHeaderInternalPadding), | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
SizedBox( | |
width: 150, | |
height: 150, | |
child: ClipOval(child: professionalHeadshot!), | |
), | |
SizedBox(width: _kSubSectionSpacing), | |
Expanded( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
if (fullName.isNotEmpty) | |
CustomText( | |
'$fullName.', | |
style: headerNameStyle, | |
), | |
if (resumeData.professionalHeadline != null && | |
resumeData.professionalHeadline!.isNotEmpty) ...[ | |
SizedBox(height: _kSubItemSpacing), | |
CustomText( | |
resumeData.professionalHeadline!.toUpperCase(), | |
style: professionalTitleStyle, | |
), | |
], | |
], | |
), | |
), | |
], | |
), | |
); | |
} else { | |
return Container( | |
color: whiteColor, | |
padding: EdgeInsets.fromLTRB( | |
pageMargin, pageMargin, pageMargin, _kHeaderInternalPadding), | |
child: Column( | |
children: [ | |
if (resumeData.professionalHeadline != null && | |
resumeData.professionalHeadline!.isNotEmpty) | |
Column( | |
children: [ | |
Divider(), | |
Padding( | |
padding: EdgeInsets.symmetric(vertical: _kSubItemSpacing), | |
child: CustomText( | |
resumeData.professionalHeadline!.toUpperCase(), | |
style: professionalTitleStyle, | |
), | |
), | |
Divider(), | |
], | |
), | |
SizedBox(height: _kSectionSpacing), | |
Row( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Stack( | |
alignment: Alignment.center, | |
children: [ | |
Container( | |
width: 160, | |
height: 160, | |
decoration: BoxDecoration( | |
color: accentColor, | |
shape: BoxShape.circle, | |
), | |
), | |
CustomText( | |
myResumeContent['headerGreeting']!, | |
style: headerHelloStyle, | |
textAlign: TextAlign.center, | |
), | |
], | |
), | |
SizedBox(width: _kSubSectionSpacing), | |
if (fullName.isNotEmpty) | |
Expanded( | |
child: CustomText( | |
'$fullName.', | |
style: headerNameStyle, | |
), | |
), | |
], | |
), | |
], | |
), | |
); | |
} | |
} | |
Widget _buildBody() { | |
return Container( | |
color: backgroundColor, | |
padding: EdgeInsets.fromLTRB( | |
pageMargin, _kBodyInternalTopPadding, pageMargin, pageMargin), | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Expanded( | |
flex: 2, | |
child: _buildLeftColumn(), | |
), | |
SizedBox(width: _kInterColumnSpacing), | |
Expanded( | |
flex: 3, | |
child: _buildRightColumn(), | |
), | |
], | |
), | |
); | |
} | |
Widget _buildLeftColumn() { | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
_buildContactInfo(), | |
SizedBox(height: _kSectionSpacing), | |
_buildEducation(), | |
SizedBox(height: _kSectionSpacing), | |
_buildSkills(), | |
SizedBox(height: _kSectionSpacing), | |
..._buildAdditionalSections(), | |
], | |
); | |
} | |
Widget _buildRightColumn() { | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (resumeData.professionalSummary != null && | |
resumeData.professionalSummary!.isNotEmpty) ...[ | |
CustomText( | |
myResumeContent['profileTitle']!, | |
style: sectionTitleStyle, | |
), | |
SizedBox(height: _kItemSpacing), | |
CustomText( | |
resumeData.professionalSummary!, | |
style: bodyTextStyle, | |
), | |
SizedBox(height: _kSectionSpacing), | |
], | |
_buildWorkExperience(), | |
if (resumeData.additionalExperiences.isNotEmpty) ...[ | |
SizedBox(height: _kSectionSpacing), | |
_buildAdditionalExperiences(), | |
] | |
], | |
); | |
} | |
Widget _buildContactInfo() { | |
final contact = resumeData.contact; | |
final address = [contact.address1, contact.address2] | |
.where((s) => s != null && s.isNotEmpty) | |
.join('\n'); | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (address.isNotEmpty) ...[ | |
CustomText(myResumeContent['addressLabel']!, style: contactLabelStyle), | |
SizedBox(height: _kTinySpacing), | |
CustomText(address, style: bodyTextStyle), | |
SizedBox(height: _kSubSectionSpacing), | |
], | |
if (contact.phone != null && contact.phone!.isNotEmpty) ...[ | |
CustomText(myResumeContent['phoneLabel']!, style: contactLabelStyle), | |
SizedBox(height: _kTinySpacing), | |
CustomText(contact.phone!, style: bodyTextStyle), | |
SizedBox(height: _kSubSectionSpacing), | |
], | |
if (contact.email != null && contact.email!.isNotEmpty) ...[ | |
CustomText(myResumeContent['emailLabel']!, style: contactLabelStyle), | |
SizedBox(height: _kTinySpacing), | |
CustomText(contact.email!, style: bodyTextStyle), | |
SizedBox(height: _kSubSectionSpacing), | |
], | |
if (contact.website != null && contact.website!.isNotEmpty) ...[ | |
CustomText(myResumeContent['websiteLabel']!, style: contactLabelStyle), | |
SizedBox(height: _kTinySpacing), | |
CustomText(contact.website!, style: bodyTextStyle), | |
SizedBox(height: _kSubSectionSpacing), | |
], | |
if (contact.socialLinks.isNotEmpty) ...[ | |
...contact.socialLinks | |
.where((link) => | |
link.type != null && | |
link.type!.isNotEmpty && | |
link.url != null && | |
link.url!.isNotEmpty) | |
.map((link) => Padding( | |
padding: EdgeInsets.only(bottom: _kTinySpacing), | |
child: CustomText( | |
'${link.type!}: ${link.url!}', | |
style: bodyTextStyle, | |
), | |
)), | |
] | |
], | |
); | |
} | |
Widget _buildEducation() { | |
if (resumeData.education.isEmpty) return SizedBox.shrink(); | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
CustomText(myResumeContent['educationTitle']!, style: sectionTitleStyle), | |
SizedBox(height: _kItemSpacing), | |
...resumeData.education.map((edu) { | |
final schoolAndDates = [edu.school, edu.dates] | |
.where((s) => s != null && s.isNotEmpty) | |
.join('\n'); | |
return Padding( | |
padding: EdgeInsets.only(bottom: _kSubSectionSpacing), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (edu.degree != null && edu.degree!.isNotEmpty) ...[ | |
CustomText(edu.degree!, style: itemTitleStyle), | |
SizedBox(height: _kTinySpacing), | |
], | |
if (schoolAndDates.isNotEmpty) ...[ | |
CustomText(schoolAndDates, style: bodyTextStyle), | |
], | |
if (edu.description != null && | |
edu.description!.isNotEmpty) ...[ | |
SizedBox(height: _kTinySpacing), | |
CustomText( | |
edu.description!, | |
style: bodyTextStyle, | |
), | |
] | |
], | |
), | |
); | |
}), | |
], | |
); | |
} | |
Widget _buildSkills() { | |
final validSkills = resumeData.skills | |
.where((s) => s.itemTitle != null && s.itemTitle!.isNotEmpty) | |
.toList(); | |
if (validSkills.isEmpty) return SizedBox.shrink(); | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
CustomText(myResumeContent['skillsTitle']!, style: sectionTitleStyle), | |
SizedBox(height: _kItemSpacing), | |
...validSkills.map((skill) => Padding( | |
padding: EdgeInsets.only(bottom: _kSubItemSpacing), | |
child: CustomTextWithDescription( | |
itemTitle: skill.itemTitle, | |
itemDescription: skill.itemDescription, | |
itemTitleStyle: bodyTextStyle, | |
itemDescriptionStyle: bodyTextStyle, | |
oneLine: false, | |
showBullet: true, | |
), | |
)), | |
], | |
); | |
} | |
List<Widget> _buildAdditionalSections() { | |
return resumeData.additionalSimpleSections | |
.where((section) => | |
section.sectionTitle != null && section.sectionTitle!.isNotEmpty) | |
.map((section) { | |
final validContent = section.content | |
.where((item) => item.itemTitle != null && item.itemTitle!.isNotEmpty) | |
.toList(); | |
if (validContent.isEmpty) return SizedBox.shrink(); | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
CustomText(section.sectionTitle!, style: sectionTitleStyle), | |
SizedBox(height: _kItemSpacing), | |
...validContent.map((item) => Padding( | |
padding: EdgeInsets.only(bottom: _kSubItemSpacing), | |
child: CustomTextWithDescription( | |
itemTitle: item.itemTitle, | |
itemDescription: item.itemDescription, | |
itemTitleStyle: itemTitleStyle, | |
itemDescriptionStyle: bodyTextStyle, | |
oneLine: false, | |
showBullet: false, | |
), | |
)), | |
SizedBox(height: _kSectionSpacing), | |
], | |
); | |
}).toList(); | |
} | |
Widget _buildWorkExperience() { | |
if (resumeData.work.isEmpty) return SizedBox.shrink(); | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
CustomText(myResumeContent['experienceTitle']!, style: sectionTitleStyle), | |
SizedBox(height: _kItemSpacing), | |
...resumeData.work.map((job) { | |
final companyLocation = [job.company, job.location] | |
.where((s) => s != null && s.isNotEmpty) | |
.join(', '); | |
final fullLine = [companyLocation, job.dates] | |
.where((s) => s.isNotEmpty) | |
.join(' · '); | |
return Padding( | |
padding: EdgeInsets.only(bottom: _kExperienceItemBottomMargin), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (fullLine.isNotEmpty) ...[ | |
CustomText(fullLine, style: datesAndCompanyStyle), | |
SizedBox(height: _kSubItemSpacing), | |
], | |
if (job.position != null && job.position!.isNotEmpty) ...[ | |
CustomText(job.position!, style: jobPositionStyle), | |
SizedBox(height: _kSubItemSpacing), | |
], | |
if (job.description != null && job.description!.isNotEmpty) | |
CustomText( | |
job.description!, | |
style: bodyTextStyle, | |
bulletedList: true, | |
), | |
], | |
), | |
); | |
}), | |
], | |
); | |
} | |
Widget _buildAdditionalExperiences() { | |
final validSections = resumeData.additionalExperiences | |
.where((s) => s.sectionTitle != null && s.sectionTitle!.isNotEmpty) | |
.toList(); | |
if (validSections.isEmpty) return SizedBox.shrink(); | |
final List<Widget> children = []; | |
for (int i = 0; i < validSections.length; i++) { | |
final section = validSections[i]; | |
children.add( | |
CustomText(section.sectionTitle!, style: sectionTitleStyle), | |
); | |
children.add(SizedBox(height: _kItemSpacing)); | |
for (final item in section.content) { | |
final subtitleLocation = [item.subtitle, item.location] | |
.where((s) => s != null && s.isNotEmpty) | |
.join(', '); | |
final fullLine = [subtitleLocation, item.dates] | |
.where((s) => s.isNotEmpty) | |
.join(' · '); | |
children.add( | |
Padding( | |
padding: EdgeInsets.only(bottom: _kExperienceItemBottomMargin), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (fullLine.isNotEmpty) ...[ | |
CustomText(fullLine, style: datesAndCompanyStyle), | |
SizedBox(height: _kSubItemSpacing), | |
], | |
if (item.title != null && item.title!.isNotEmpty) ...[ | |
CustomText(item.title!, style: jobPositionStyle), | |
SizedBox(height: _kSubItemSpacing), | |
], | |
if (item.description != null && item.description!.isNotEmpty) | |
CustomText( | |
item.description!, | |
style: bodyTextStyle, | |
bulletedList: true, | |
), | |
], | |
), | |
), | |
); | |
} | |
if (i < validSections.length - 1) { | |
children.add(SizedBox(height: _kSectionSpacing)); | |
} | |
} | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: children, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment