11import 'package:flutter/material.dart' ;
22import 'package:portfolio/layout.dart' ;
33import 'package:portfolio/theme.dart' ;
4+ import 'package:portfolio/widgets.dart' ;
5+ import 'package:url_launcher/link.dart' ;
46
57class Project {
68 Project ({
@@ -9,6 +11,7 @@ class Project {
911 required this .image,
1012 this .startDate,
1113 this .endDate,
14+ this .links = const [],
1215 }) : // Start date must be before end date
1316 assert (startDate == null ||
1417 endDate == null ||
@@ -31,6 +34,26 @@ class Project {
3134
3235 /// The end date of the project. If null, the project is ongoing.
3336 final DateTime ? endDate;
37+
38+ final List <ProjectLink > links;
39+ }
40+
41+ class ProjectLink {
42+ /// A link to a project, such as a website or a repository.
43+ ProjectLink ({
44+ required this .title,
45+ required this .icon,
46+ required this .uri,
47+ });
48+
49+ /// A title describing the link
50+ final String title;
51+
52+ /// An icon representing the link
53+ final ImageProvider icon;
54+
55+ /// The URI that the link opens
56+ final Uri uri;
3457}
3558
3659class ProjectsSection extends StatelessWidget {
@@ -43,6 +66,20 @@ class ProjectsSection extends StatelessWidget {
4366 "A mobile app that offers everything a musician needs for transcribing, practicing and performing." ,
4467 image: AssetImage ("assets/projects/treble_clef.png" ),
4568 startDate: DateTime (2022 ),
69+ links: [
70+ ProjectLink (
71+ title: "Google Play" ,
72+ icon: AssetImage ("assets/icons/google-play.png" ),
73+ uri: Uri .parse (
74+ "https://play.google.com/store/apps/details?id=se.agardh.musbx&pcampaignid=portfolio" ),
75+ ),
76+ ProjectLink (
77+ title: "App Store" ,
78+ icon: AssetImage ("assets/icons/app-store.png" ),
79+ uri: Uri .parse (
80+ "https://apps.apple.com/se/app/musicians-toolbox/id1670009655" ),
81+ ),
82+ ],
4683 ),
4784 Project (
4885 title: "Dirma" ,
@@ -51,6 +88,13 @@ class ProjectsSection extends StatelessWidget {
5188 image: AssetImage ("assets/projects/dirma.png" ),
5289 startDate: DateTime (2023 ),
5390 endDate: DateTime (2023 ),
91+ links: [
92+ ProjectLink (
93+ title: "Website" ,
94+ icon: IconImage (Icons .public),
95+ uri: Uri .parse ("https://dirma.se" ),
96+ ),
97+ ],
5498 ),
5599 Project (
56100 title: "Märklin Bluetooth controller" ,
@@ -59,6 +103,34 @@ class ProjectsSection extends StatelessWidget {
59103 image: AssetImage ("assets/projects/car.png" ),
60104 startDate: DateTime (2021 ),
61105 endDate: DateTime (2022 ),
106+ links: [
107+ ProjectLink (
108+ title: "GitHub" ,
109+ icon: AssetImage ("assets/icons/github-mark.png" ),
110+ uri: Uri .parse ("https://github.com/bemain/marklin_client" ),
111+ ),
112+ ],
113+ ),
114+ Project (
115+ title: "Oxdjuptet lajv" ,
116+ description:
117+ "A mobile app used during the larp I arrange yearly at Oxdjupet." ,
118+ image: AssetImage ("assets/projects/oxdjupet.png" ),
119+ startDate: DateTime (2021 ),
120+ links: [
121+ ProjectLink (
122+ title: "Google Play" ,
123+ icon: AssetImage ("assets/icons/google-play.png" ),
124+ uri: Uri .parse (
125+ "https://play.google.com/store/apps/details?id=se.agardh.lajv&pcampaignid=web_sharehttps://play.google.com/store/apps/details?id=se.agardh.lajv&pcampaignid=portfolio" ),
126+ ),
127+ ProjectLink (
128+ title: "App Store" ,
129+ icon: AssetImage ("assets/icons/app-store.png" ),
130+ uri: Uri .parse (
131+ "https://apps.apple.com/se/app/oxdjupet-lajv/id6504813711" ),
132+ ),
133+ ],
62134 ),
63135 ];
64136
@@ -94,7 +166,7 @@ class ProjectsSection extends StatelessWidget {
94166 _buildTitle (context),
95167 const SizedBox (height: 24 ),
96168 GridView .extent (
97- childAspectRatio: 0.9 ,
169+ childAspectRatio: 0.8 ,
98170 maxCrossAxisExtent: 360 ,
99171 mainAxisSpacing: 8 ,
100172 crossAxisSpacing: 8 ,
@@ -154,6 +226,41 @@ class ProjectsSection extends StatelessWidget {
154226 padding: EdgeInsets .symmetric (horizontal: 6 ),
155227 child: _buildProjectText (context, project),
156228 ),
229+ const SizedBox (height: 4 ),
230+ for (ProjectLink link in project.links)
231+ Align (
232+ alignment: Alignment .centerLeft,
233+ child: Link (
234+ uri: link.uri,
235+ builder: (context, followLink) {
236+ return TextButton .icon (
237+ onPressed: followLink,
238+ icon: Image (
239+ image: link.icon,
240+ color: descriptionTextStyle (context)? .color,
241+ width: 20 ,
242+ height: 20 ,
243+ ),
244+ label: Row (
245+ spacing: 8 ,
246+ mainAxisSize: MainAxisSize .min,
247+ children: [
248+ Text (
249+ link.title,
250+ style: TextStyle (
251+ color: descriptionTextStyle (context)? .color,
252+ ),
253+ ),
254+ Icon (
255+ Icons .open_in_new,
256+ color: descriptionTextStyle (context)? .color,
257+ ),
258+ ],
259+ ),
260+ );
261+ },
262+ ),
263+ ),
157264 ],
158265 ),
159266 ),
@@ -176,23 +283,27 @@ class ProjectsSection extends StatelessWidget {
176283 style: descriptionTextStyle (context),
177284 ),
178285 if (project.startDate != null )
179- RichText (
180- text: TextSpan (
181- style: descriptionTextStyle (context),
182- children: [
183- WidgetSpan (
184- alignment: PlaceholderAlignment .middle,
185- child: Icon (
186- Icons .timeline,
187- color: descriptionTextStyle (context)? .color,
188- ),
189- ),
190- TextSpan (text: " ${project .startDate ?.year }" ),
191- if (project.endDate != project.startDate)
192- TextSpan (
193- text: " - ${project .endDate ?.year ?? "present" }" ,
286+ Padding (
287+ padding: EdgeInsetsDirectional .symmetric (horizontal: 6 ),
288+ child: RichText (
289+ text: TextSpan (
290+ style: descriptionTextStyle (context),
291+ children: [
292+ WidgetSpan (
293+ alignment: PlaceholderAlignment .middle,
294+ child: Icon (
295+ Icons .timeline,
296+ size: 20 ,
297+ color: descriptionTextStyle (context)? .color,
298+ ),
194299 ),
195- ],
300+ TextSpan (text: " ${project .startDate ?.year }" ),
301+ if (project.endDate != project.startDate)
302+ TextSpan (
303+ text: " - ${project .endDate ?.year ?? "present" }" ,
304+ ),
305+ ],
306+ ),
196307 ),
197308 ),
198309 ],
0 commit comments