Skip to content

Instantly share code, notes, and snippets.

@ben953
Created June 8, 2025 06:21
Show Gist options
  • Save ben953/e3d24ee6b64276fc9ef5c872348f5a4f to your computer and use it in GitHub Desktop.
Save ben953/e3d24ee6b64276fc9ef5c872348f5a4f to your computer and use it in GitHub Desktop.
Sample
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