Compare commits
10 Commits
b221164b8d
...
cf79defe49
| Author | SHA1 | Date | |
|---|---|---|---|
| cf79defe49 | |||
| 9982c3396a | |||
| e22fe2e58f | |||
| f91c8100be | |||
| 7f9374a768 | |||
| 7bb7037618 | |||
| 06da803497 | |||
| 7a1bbfe365 | |||
| d715b4b201 | |||
| bacf168d09 |
20
.gitignore
vendored
20
.gitignore
vendored
@@ -46,6 +46,23 @@ analysis_benchmark.json
|
|||||||
# packages file containing multi-root paths
|
# packages file containing multi-root paths
|
||||||
.packages.generated
|
.packages.generated
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
/dev/docs/api_docs.zip
|
||||||
|
/dev/docs/flutter.docs.zip
|
||||||
|
/dev/docs/lib/
|
||||||
|
/dev/docs/pubspec.yaml
|
||||||
|
/dev/integration_tests/**/xcuserdata
|
||||||
|
/dev/integration_tests/**/Pods
|
||||||
|
/packages/flutter/coverage/
|
||||||
|
version
|
||||||
|
analysis_benchmark.json
|
||||||
|
|
||||||
|
# packages file containing multi-root paths
|
||||||
|
.packages.generated
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
@@ -137,3 +154,6 @@ app.*.symbols
|
|||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
!/dev/ci/**/Gemfile.lock
|
!/dev/ci/**/Gemfile.lock
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
content.json
|
||||||
BIN
assets/profile.jpg
Executable file
BIN
assets/profile.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
@@ -1,11 +1,43 @@
|
|||||||
import 'package:flutter/material.dart';
|
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';
|
||||||
|
|
||||||
class LandingPage extends StatelessWidget {
|
class LandingPage extends StatefulWidget {
|
||||||
const LandingPage({super.key});
|
const LandingPage({super.key});
|
||||||
|
|
||||||
static const String routeName = '/';
|
static const String routeName = '/';
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LandingPage> createState() => _LandingPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LandingPageState extends State<LandingPage> {
|
||||||
|
bool loadingDone = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await ContentProvider.init();
|
||||||
|
setState(() => loadingDone = true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getPageWidth() {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
// if (width < Breakpoints.sm) return width;
|
||||||
|
if (width < Breakpoints.md) return width * 0.95;
|
||||||
|
if (width < Breakpoints.lg) return width * 0.8;
|
||||||
|
if (width < Breakpoints.xl) return width * 0.7;
|
||||||
|
if (width < Breakpoints.xl2) return width * 0.6;
|
||||||
|
return width * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Landing'),
|
title: const Text('Landing'),
|
||||||
@@ -13,6 +45,77 @@ class LandingPage extends StatelessWidget {
|
|||||||
TextButton(onPressed: () {}, child: const Text('Source Code')),
|
TextButton(onPressed: () {}, child: const Text('Source Code')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
body: !loadingDone
|
||||||
|
// While the content is being loaded from JSON, show a LoadingIndicator
|
||||||
|
? const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
|
||||||
|
Text('Loading...')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: (MediaQuery.of(context).size.width -
|
||||||
|
_getPageWidth()) /
|
||||||
|
2,
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 50),
|
||||||
|
child: Profile(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: SizedBox(
|
||||||
|
width: (MediaQuery.of(context).size.width -
|
||||||
|
_getPageWidth()) /
|
||||||
|
2,
|
||||||
|
child: screenWidth >= Breakpoints.xl2
|
||||||
|
? Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 50),
|
||||||
|
child: ContentBox(
|
||||||
|
title: 'Fähigkeiten',
|
||||||
|
content: ContentProvider.skills,
|
||||||
|
contentType: ContentType.skills,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: _getPageWidth(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ContentBox(
|
||||||
|
title: 'Arbeitserfahrung',
|
||||||
|
content: ContentProvider.experience,
|
||||||
|
contentType: ContentType.experience,
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 15)),
|
||||||
|
ContentBox(
|
||||||
|
title: 'Bildungsweg',
|
||||||
|
content: ContentProvider.education,
|
||||||
|
contentType: ContentType.education,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
lib/services/breakpoints.dart
Normal file
7
lib/services/breakpoints.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
class Breakpoints {
|
||||||
|
static const sm = 640;
|
||||||
|
static const md = 768;
|
||||||
|
static const lg = 1024;
|
||||||
|
static const xl = 1280;
|
||||||
|
static const xl2 = 1536;
|
||||||
|
}
|
||||||
27
lib/services/content_provider.dart
Normal file
27
lib/services/content_provider.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class ContentProvider {
|
||||||
|
ContentProvider._();
|
||||||
|
|
||||||
|
static const String _jsonPath = 'assets/content.json';
|
||||||
|
|
||||||
|
static Future<bool> init() async {
|
||||||
|
try {
|
||||||
|
String file = await rootBundle.loadString(_jsonPath);
|
||||||
|
_content = json.decode(file);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _content = {};
|
||||||
|
|
||||||
|
static List<dynamic> get experience => _content['experience'];
|
||||||
|
|
||||||
|
static List<dynamic> get education => _content['education'];
|
||||||
|
|
||||||
|
static List<dynamic> get skills => _content['skills'];
|
||||||
|
}
|
||||||
69
lib/widgets/content_list_tile.dart
Normal file
69
lib/widgets/content_list_tile.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:resume/services/breakpoints.dart';
|
||||||
|
|
||||||
|
class ContentListTile extends StatelessWidget {
|
||||||
|
const ContentListTile(
|
||||||
|
{super.key,
|
||||||
|
this.name,
|
||||||
|
this.location,
|
||||||
|
this.title,
|
||||||
|
this.description,
|
||||||
|
this.startDate,
|
||||||
|
this.endDate});
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
final String? location;
|
||||||
|
final String? title;
|
||||||
|
final String? description;
|
||||||
|
final String? startDate;
|
||||||
|
final String? endDate;
|
||||||
|
|
||||||
|
@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 ? Text(description!) : null,
|
||||||
|
trailing: startDate != null && endDate != null
|
||||||
|
? Text(_getTimeString(startDate!, endDate!))
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December'
|
||||||
|
];
|
||||||
|
}
|
||||||
69
lib/widgets/content_widget.dart
Normal file
69
lib/widgets/content_widget.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
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 }
|
||||||
40
lib/widgets/profile.dart
Normal file
40
lib/widgets/profile.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Profile extends StatelessWidget {
|
||||||
|
const Profile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(180),
|
||||||
|
child: Image.asset('assets/profile.jpg'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
|
||||||
|
Text(
|
||||||
|
'Marco Skupin',
|
||||||
|
style: Theme.of(context).textTheme.displayMedium,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
|
||||||
|
Text(
|
||||||
|
'Master of Science',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
|
||||||
|
Text(
|
||||||
|
'marco@skup.in',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
lib/widgets/skill_list_tile.dart
Normal file
31
lib/widgets/skill_list_tile.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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)),
|
||||||
|
if (percentage != null)
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: double.parse(percentage!) / 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
pubspec.yaml
70
pubspec.yaml
@@ -1,90 +1,26 @@
|
|||||||
name: resume
|
name: resume
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
# The following line prevents the package from being accidentally published to
|
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^4.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
assets:
|
||||||
# assets:
|
- assets/content.json
|
||||||
# - images/a_dot_burr.jpeg
|
- assets/profile.jpg
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/to/asset-from-package
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/to/font-from-package
|
|
||||||
Reference in New Issue
Block a user