Compare commits
34 Commits
b221164b8d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b66da23887 | |||
| 6587828a0b | |||
| a984fc15b0 | |||
| 0d9118ab3e | |||
| 85b5d4dc7c | |||
| ebf1246a55 | |||
| 8e91388ecf | |||
| b9033014c8 | |||
| 0a877525aa | |||
| ff97898b90 | |||
| c523b7495f | |||
| 4c2162a158 | |||
| ee705938f7 | |||
| 35e1171623 | |||
| 6ebfa77416 | |||
| b8840ffe0c | |||
| c62dbbb707 | |||
| 7fac0160e0 | |||
| 3ba6f9d714 | |||
| 683f0174ef | |||
| fc1823aec0 | |||
| 634ecce4d9 | |||
| 69675f42e2 | |||
| da4a057fd7 | |||
| 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/
|
||||||
BIN
assets/de_icon.png
Normal file
BIN
assets/de_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 891 B |
BIN
assets/fonts/radley/Radley-Italic.ttf
Normal file
BIN
assets/fonts/radley/Radley-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/radley/Radley-Regular.ttf
Normal file
BIN
assets/fonts/radley/Radley-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/gb_icon.png
Normal file
BIN
assets/gb_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/profile.jpg
Executable file
BIN
assets/profile.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
3
l10n.yaml
Normal file
3
l10n.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
8
lib/constants.dart
Normal file
8
lib/constants.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
enum ContentType {
|
||||||
|
experience,
|
||||||
|
education,
|
||||||
|
skills,
|
||||||
|
language,
|
||||||
|
text,
|
||||||
|
generalSkills
|
||||||
|
}
|
||||||
28
lib/l10n/app_de.arb
Normal file
28
lib/l10n/app_de.arb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"german": "Deutsch",
|
||||||
|
"english": "Englisch",
|
||||||
|
"resume": "Lebenslauf",
|
||||||
|
"skills": "Fähigkeiten",
|
||||||
|
"languages": "Sprachen",
|
||||||
|
"mother_tongue": "Muttersprache",
|
||||||
|
"very_good": "Sehr gut",
|
||||||
|
"about_me": "Über mich",
|
||||||
|
"additional_skills": "Weitere Kenntnisse",
|
||||||
|
"work_experience": "Arbeitserfahrung",
|
||||||
|
"education": "Bildungsweg",
|
||||||
|
"source_code": "Quellcode",
|
||||||
|
|
||||||
|
"@_months": {},
|
||||||
|
"january": "Januar",
|
||||||
|
"february": "Februar",
|
||||||
|
"march": "März",
|
||||||
|
"april": "April",
|
||||||
|
"may": "Mai",
|
||||||
|
"june": "Juni",
|
||||||
|
"july": "Juli",
|
||||||
|
"august": "August",
|
||||||
|
"september": "September",
|
||||||
|
"october": "Oktober",
|
||||||
|
"november": "November",
|
||||||
|
"december": "Dezember"
|
||||||
|
}
|
||||||
28
lib/l10n/app_en.arb
Normal file
28
lib/l10n/app_en.arb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"german": "German",
|
||||||
|
"english": "English",
|
||||||
|
"resume": "Resume",
|
||||||
|
"skills": "Skills",
|
||||||
|
"languages": "Languages",
|
||||||
|
"mother_tongue": "Native",
|
||||||
|
"very_good": "Very good",
|
||||||
|
"about_me": "About me",
|
||||||
|
"additional_skills": "Additional Skills",
|
||||||
|
"work_experience": "Work experience",
|
||||||
|
"education": "Education",
|
||||||
|
"source_code": "Source Code",
|
||||||
|
|
||||||
|
"@_months": {},
|
||||||
|
"january": "January",
|
||||||
|
"february": "February",
|
||||||
|
"march": "March",
|
||||||
|
"april": "April",
|
||||||
|
"may": "May",
|
||||||
|
"june": "June",
|
||||||
|
"july": "July",
|
||||||
|
"august": "August",
|
||||||
|
"september": "September",
|
||||||
|
"october": "October",
|
||||||
|
"november": "November",
|
||||||
|
"december": "December"
|
||||||
|
}
|
||||||
@@ -1,26 +1,60 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:resume/pages/landing_page.dart';
|
import 'package:resume/pages/landing_page.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:resume/providers/locale_provider.dart';
|
||||||
|
import 'package:resume/providers/content_provider.dart';
|
||||||
|
import './services/tools.dart';
|
||||||
|
import 'theme.dart' show darkTheme;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const Resume());
|
final String initialPath = Uri.base.path;
|
||||||
|
final String defaultLocale = Tools.getLocaleFromPath(initialPath);
|
||||||
|
runApp(
|
||||||
|
MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) =>
|
||||||
|
LocaleProvider(defaultLocale: Locale(defaultLocale)),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(create: (context) => ContentProvider()),
|
||||||
|
],
|
||||||
|
child: const Resume(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Resume extends StatelessWidget {
|
class Resume extends StatefulWidget {
|
||||||
const Resume({super.key});
|
const Resume({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Resume> createState() => _ResumeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ResumeState extends State<Resume> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return Consumer<LocaleProvider>(
|
||||||
|
builder: (context, localeProvider, child) => MaterialApp(
|
||||||
title: 'Resume',
|
title: 'Resume',
|
||||||
theme: ThemeData(
|
theme: darkTheme,
|
||||||
colorScheme: ColorScheme.fromSeed(
|
localizationsDelegates: const [
|
||||||
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
AppLocalizations.delegate,
|
||||||
useMaterial3: true,
|
GlobalMaterialLocalizations.delegate,
|
||||||
),
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('en'),
|
||||||
|
Locale('de'),
|
||||||
|
],
|
||||||
|
locale: localeProvider.locale,
|
||||||
routes: {
|
routes: {
|
||||||
'/': (context) => const LandingPage(),
|
'/': (context) => const LandingPage(),
|
||||||
},
|
},
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,251 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:resume/providers/locale_provider.dart';
|
||||||
|
import 'package:resume/services/breakpoints.dart';
|
||||||
|
import 'package:resume/widgets/language_dropdown.dart';
|
||||||
|
import 'package:resume/widgets/profile.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:resume/constants.dart' show ContentType;
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
class LandingPage extends StatelessWidget {
|
import '../providers/content_provider.dart';
|
||||||
|
import '../widgets/content_block.dart';
|
||||||
|
|
||||||
|
class LandingPage extends StatefulWidget {
|
||||||
const LandingPage({super.key});
|
const LandingPage({super.key});
|
||||||
|
|
||||||
static const String routeName = '/';
|
static const String routeName = '/';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<LandingPage> createState() => _LandingPageState();
|
||||||
return Scaffold(
|
}
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Landing'),
|
class _LandingPageState extends State<LandingPage> {
|
||||||
actions: [
|
bool loadingDone = false;
|
||||||
TextButton(onPressed: () {}, child: const Text('Source Code')),
|
late ContentProvider contentProvider;
|
||||||
|
late Locale currentLocale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await Provider.of<ContentProvider>(context, listen: false)
|
||||||
|
.loadContent(context);
|
||||||
|
setState(() => loadingDone = true);
|
||||||
|
});
|
||||||
|
currentLocale = context.read<LocaleProvider>().locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getMainContentWidth() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getSidebarWidth() =>
|
||||||
|
(MediaQuery.of(context).size.width - _getMainContentWidth()) / 2;
|
||||||
|
|
||||||
|
Widget _getSideBar() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.skills,
|
||||||
|
contentType: ContentType.skills,
|
||||||
|
content: contentProvider.getContent(ContentType.skills),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.languages,
|
||||||
|
contentType: ContentType.language,
|
||||||
|
content: contentProvider.getContent(ContentType.language),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _getMainContent() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.work_experience,
|
||||||
|
contentType: ContentType.experience,
|
||||||
|
content: contentProvider.getContent(ContentType.experience),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.education,
|
||||||
|
contentType: ContentType.education,
|
||||||
|
content: contentProvider.getContent(ContentType.education),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.additional_skills,
|
||||||
|
contentType: ContentType.generalSkills,
|
||||||
|
content: contentProvider.getContent(ContentType.generalSkills),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
ContentBlock(
|
||||||
|
blockTitle: AppLocalizations.of(context)!.about_me,
|
||||||
|
contentType: ContentType.text,
|
||||||
|
content: contentProvider.getContent(ContentType.text),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
|
||||||
|
if (constraints.maxWidth > Breakpoints.xl2) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _getSidebarWidth(),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Profile(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _getSidebarWidth(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: _getSideBar(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: _getMainContentWidth(),
|
||||||
|
child: _getMainContent(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (constraints.maxWidth > Breakpoints.xl) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Profile(),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: _getSideBar(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 900,
|
||||||
|
child: _getMainContent(),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(right: 25)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (constraints.maxWidth > Breakpoints.lg) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Profile(),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
_getSideBar(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 650,
|
||||||
|
child: _getMainContent(),
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(right: 25)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Profile(),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
_getMainContent(),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 25)),
|
||||||
|
_getSideBar(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
contentProvider = context.read<ContentProvider>();
|
||||||
|
|
||||||
|
if (currentLocale != context.read<LocaleProvider>().locale) {
|
||||||
|
loadingDone = false;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await Provider.of<ContentProvider>(context, listen: false)
|
||||||
|
.loadContent(context);
|
||||||
|
setState(() {
|
||||||
|
currentLocale = context.read<LocaleProvider>().locale;
|
||||||
|
loadingDone = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context)!.resume),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _launchURL,
|
||||||
|
child: Text(AppLocalizations.of(context)!.source_code)),
|
||||||
|
const LanguageDropdown(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: !loadingDone
|
||||||
|
? const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
|
||||||
|
Text('Loading...')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: _buildLayout,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchURL() async {
|
||||||
|
final Uri url = Uri.parse('https://git.skup.in/marco/resume');
|
||||||
|
await launchUrl(url);
|
||||||
}
|
}
|
||||||
|
|||||||
58
lib/providers/content_provider.dart
Normal file
58
lib/providers/content_provider.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:resume/constants.dart';
|
||||||
|
import 'package:resume/providers/locale_provider.dart';
|
||||||
|
|
||||||
|
class ContentProvider extends ChangeNotifier {
|
||||||
|
static final ContentProvider _instance = ContentProvider._();
|
||||||
|
factory ContentProvider() => _instance;
|
||||||
|
ContentProvider._();
|
||||||
|
|
||||||
|
static const String _baseJsonPath = 'assets/content/content';
|
||||||
|
|
||||||
|
Map<String, dynamic> _content = {
|
||||||
|
'experience': <List<dynamic>>[],
|
||||||
|
'education': <List<dynamic>>[],
|
||||||
|
'skills': <List<dynamic>>[],
|
||||||
|
'text': <String>[],
|
||||||
|
'general_skills': <String>[],
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<bool> loadContent(BuildContext context) async {
|
||||||
|
String currentLocale = context.read<LocaleProvider>().locale.languageCode;
|
||||||
|
final String localizedPath = '${_baseJsonPath}_$currentLocale.json';
|
||||||
|
|
||||||
|
try {
|
||||||
|
String file = await rootBundle.loadString(localizedPath);
|
||||||
|
_content = json.decode(file);
|
||||||
|
notifyListeners();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
// If localized version fails, fall back to default German
|
||||||
|
String file = await rootBundle.loadString('${_baseJsonPath}_de.json');
|
||||||
|
_content = json.decode(file);
|
||||||
|
notifyListeners();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T getContent<T>(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;
|
||||||
|
case ContentType.generalSkills:
|
||||||
|
return _content['general_skills'] as T;
|
||||||
|
default:
|
||||||
|
return [] as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
lib/providers/locale_provider.dart
Normal file
14
lib/providers/locale_provider.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LocaleProvider extends ChangeNotifier {
|
||||||
|
LocaleProvider({Locale? defaultLocale})
|
||||||
|
: _locale = defaultLocale ?? const Locale('de');
|
||||||
|
Locale _locale = const Locale('de');
|
||||||
|
|
||||||
|
Locale get locale => _locale;
|
||||||
|
|
||||||
|
void setLocale(Locale locale) {
|
||||||
|
_locale = locale;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
108
lib/services/tools.dart
Normal file
108
lib/services/tools.dart
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class Tools {
|
||||||
|
Tools._();
|
||||||
|
|
||||||
|
/// Builds a formatted time string from two date strings in the format 'YYYY-MM'.
|
||||||
|
///
|
||||||
|
/// Returns a string in the format 'Month Year - Month Year' using localized month names.
|
||||||
|
/// Returns an empty string if either input is empty or invalid.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [startDate]: String in 'YYYY-MM' format (e.g., '2024-03')
|
||||||
|
/// - [endDate]: String in 'YYYY-MM' format (e.g., '2024-12')
|
||||||
|
/// - [context]: BuildContext for accessing localizations
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final timeString = buildTimeString('2024-03', '2024-12', context);
|
||||||
|
/// // Returns "March 2024 - December 2024" (or localized equivalent)
|
||||||
|
/// ```
|
||||||
|
static String buildTimeString(
|
||||||
|
String startDate, String endDate, BuildContext context) {
|
||||||
|
// Check for empty or null inputs
|
||||||
|
if (startDate.isEmpty || endDate.isEmpty) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate date format
|
||||||
|
if (!startDate.contains(RegExp(r'^\d{4}-\d{2}$')) ||
|
||||||
|
!endDate.contains(RegExp(r'^\d{4}-\d{2}$'))) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse dates with validation
|
||||||
|
final firstDate = DateTime.tryParse('$startDate-01');
|
||||||
|
final secondDate = DateTime.tryParse('$endDate-01');
|
||||||
|
|
||||||
|
// Check if parsing was successful
|
||||||
|
if (firstDate == null || secondDate == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the formatted string
|
||||||
|
return '${getLocalizedMonth(context, firstDate.month)} ${firstDate.year} - '
|
||||||
|
'${getLocalizedMonth(context, secondDate.month)} ${secondDate.year}';
|
||||||
|
} catch (e) {
|
||||||
|
// Handle any unexpected errors
|
||||||
|
debugPrint('Error building time string: $e');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the localized month name as a [String] based on the provided month number.
|
||||||
|
///
|
||||||
|
/// The [context] is used to access the app's localizations.
|
||||||
|
/// The [monthNumber] must be between 1 and 12, where 1 represents January
|
||||||
|
/// and 12 represents December.
|
||||||
|
///
|
||||||
|
/// Throws an [ArgumentError] if the month number is not between 1 and 12.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final monthName = getLocalizedMonth(context, 3); // Returns "March" or "März"
|
||||||
|
/// ```
|
||||||
|
static String getLocalizedMonth(BuildContext context, int monthNumber) {
|
||||||
|
final localizations = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
switch (monthNumber) {
|
||||||
|
case 1:
|
||||||
|
return localizations.january;
|
||||||
|
case 2:
|
||||||
|
return localizations.february;
|
||||||
|
case 3:
|
||||||
|
return localizations.march;
|
||||||
|
case 4:
|
||||||
|
return localizations.april;
|
||||||
|
case 5:
|
||||||
|
return localizations.may;
|
||||||
|
case 6:
|
||||||
|
return localizations.june;
|
||||||
|
case 7:
|
||||||
|
return localizations.july;
|
||||||
|
case 8:
|
||||||
|
return localizations.august;
|
||||||
|
case 9:
|
||||||
|
return localizations.september;
|
||||||
|
case 10:
|
||||||
|
return localizations.october;
|
||||||
|
case 11:
|
||||||
|
return localizations.november;
|
||||||
|
case 12:
|
||||||
|
return localizations.december;
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Month number must be between 1 and 12');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getLocaleFromPath(String path) {
|
||||||
|
if (path.startsWith('/de')) {
|
||||||
|
return 'de';
|
||||||
|
} else if (path.startsWith('/en')) {
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
return 'de'; // Default fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lib/theme.dart
Normal file
10
lib/theme.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
ThemeData get darkTheme => ThemeData(
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xAAbb71fc),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
fontFamily: 'SourceSerif4',
|
||||||
|
);
|
||||||
84
lib/widgets/content_block.dart
Normal file
84
lib/widgets/content_block.dart
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
class ContentBlock extends StatelessWidget {
|
||||||
|
const ContentBlock({
|
||||||
|
super.key,
|
||||||
|
required this.blockTitle,
|
||||||
|
required this.contentType,
|
||||||
|
required this.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String blockTitle;
|
||||||
|
final ContentType contentType;
|
||||||
|
final dynamic content;
|
||||||
|
|
||||||
|
Widget get _getContentWidget {
|
||||||
|
if (contentType == ContentType.language) {
|
||||||
|
return const LanguageWidget();
|
||||||
|
} else if (contentType == ContentType.text ||
|
||||||
|
contentType == ContentType.generalSkills) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text(content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// List-based content-blocks
|
||||||
|
List<Widget> 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'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
lib/widgets/content_list_tile.dart
Normal file
184
lib/widgets/content_list_tile.dart
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
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.education(
|
||||||
|
{super.key,
|
||||||
|
required this.name,
|
||||||
|
required this.location,
|
||||||
|
required this.title,
|
||||||
|
required this.startDate,
|
||||||
|
required this.endDate})
|
||||||
|
: description = '',
|
||||||
|
percentage = '',
|
||||||
|
_contentType = ContentType.education;
|
||||||
|
|
||||||
|
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: 4,
|
||||||
|
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) const Text(' - '),
|
||||||
|
if (Breakpoints.xl < screenWidth)
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style:
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (Breakpoints.xl >= screenWidth)
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
titleAlignment: ListTileTitleAlignment.titleHeight,
|
||||||
|
subtitle: Breakpoints.sm >= screenWidth
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
Tools.buildTimeString(startDate, endDate, context),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
trailing: Breakpoints.sm < screenWidth
|
||||||
|
? Text(
|
||||||
|
Tools.buildTimeString(startDate, endDate, context),
|
||||||
|
style:
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||||
|
)
|
||||||
|
: 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) const Text(' - '),
|
||||||
|
if (Breakpoints.xl < screenWidth)
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style:
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (Breakpoints.xl >= screenWidth)
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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, context),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(description),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Breakpoints.sm < screenWidth
|
||||||
|
? Text(
|
||||||
|
Tools.buildTimeString(startDate, endDate, context),
|
||||||
|
style:
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
lib/widgets/language_dropdown.dart
Normal file
52
lib/widgets/language_dropdown.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../providers/locale_provider.dart';
|
||||||
|
|
||||||
|
class LanguageDropdown extends StatefulWidget {
|
||||||
|
const LanguageDropdown({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LanguageDropdown> createState() => _LanguageDropdownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LanguageDropdownState extends State<LanguageDropdown> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
LocaleProvider localeProvider = Provider.of<LocaleProvider>(context);
|
||||||
|
return DropdownButton<String>(
|
||||||
|
value: localeProvider.locale.languageCode,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'de',
|
||||||
|
child: getMenuItem(
|
||||||
|
AppLocalizations.of(context)!.german,
|
||||||
|
'assets/de_icon.png',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'en',
|
||||||
|
child: getMenuItem(
|
||||||
|
AppLocalizations.of(context)!.english,
|
||||||
|
'assets/gb_icon.png',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
localeProvider.setLocale(Locale(value));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getMenuItem(String label, String imagePath) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(label),
|
||||||
|
const Padding(padding: EdgeInsets.only(right: 8)),
|
||||||
|
Image.asset(imagePath, width: 30),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
lib/widgets/language_widget.dart
Normal file
52
lib/widgets/language_widget.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class LanguageWidget extends StatelessWidget {
|
||||||
|
const LanguageWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/de_icon.png',
|
||||||
|
width: 50,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(right: 15)),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!.german,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
Text(AppLocalizations.of(context)!.mother_tongue,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(bottom: 8)),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/gb_icon.png',
|
||||||
|
width: 50,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.only(right: 15)),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!.english,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
Text(AppLocalizations.of(context)!.very_good,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
lib/widgets/profile.dart
Normal file
46
lib/widgets/profile.dart
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Profile extends StatelessWidget {
|
||||||
|
const Profile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
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',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.displayMedium,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
|
||||||
|
Text(
|
||||||
|
'Master of Science',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 5)),
|
||||||
|
Text(
|
||||||
|
'marco@skup.in',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
pubspec.yaml
90
pubspec.yaml
@@ -1,90 +1,50 @@
|
|||||||
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
|
||||||
|
url_launcher: ^6.3.1
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
intl: any
|
||||||
|
provider: ^6.1.2
|
||||||
|
|
||||||
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:
|
||||||
|
generate: true
|
||||||
# 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/content_de.json
|
||||||
# - images/a_dot_burr.jpeg
|
- assets/content/content_en.json
|
||||||
# - images/a_dot_ham.jpeg
|
- assets/profile.jpg
|
||||||
|
- assets/de_icon.png
|
||||||
|
- assets/gb_icon.png
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
fonts:
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
- family: Radley
|
||||||
|
fonts:
|
||||||
|
- asset: assets/fonts/radley/Radley-Regular.ttf
|
||||||
|
style: normal
|
||||||
|
- asset: assets/fonts/radley/Radley-Italic.ttf
|
||||||
|
style: italic
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
- family: SourceSerif4
|
||||||
# https://flutter.dev/to/asset-from-package
|
fonts:
|
||||||
|
- asset: assets/fonts/sourceserif4/SourceSerif4-VariableFont_opsz,wght.ttf
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
style: normal
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
- asset: assets/fonts/sourceserif4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
style: italic
|
||||||
# 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
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
<!--
|
<!--
|
||||||
If you are serving your web app in a path other than the root, change the
|
If you are serving your web app in a path other than the root, change the
|
||||||
href value below to reflect the base path you are serving from.
|
href value below to reflect the base path you are serving from.
|
||||||
|
|||||||
2
web/robots.txt
Normal file
2
web/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
Reference in New Issue
Block a user