Add multi-language option

This commit is contained in:
Christoph Hagen 2023-08-20 13:11:13 +02:00
parent 55de8ada91
commit 911fc0c8f8
7 changed files with 152 additions and 27 deletions

View File

@ -11,7 +11,7 @@ struct CV: View {
}
var body: some View {
VStack(alignment: .leading) {
VStack {
TopView(info: info.top, style: style.header)
.frame(height: style.header.height)
Rectangle()
@ -53,13 +53,7 @@ struct CV: View {
}
}
Spacer(minLength: 0)
HStack {
VStack(alignment: .leading) {
ForEach(info.footer) { text in
Text(text)
}
}
}
Text(info.footer)
.font(.footnote)
.foregroundColor(.secondary)
}
@ -70,7 +64,7 @@ struct CV: View {
struct CV_Previews: PreviewProvider {
static var previews: some View {
CV(info: cvInfo, style: cvStyle)
CV(info: cvInfoEnglish, style: cvStyle)
.previewLayout(.fixed(width: 600, height: 600 * sqrt(2)))
}
}

View File

@ -6,12 +6,12 @@ struct ContentView: View {
@Environment(\.colorScheme)
var defaultColorScheme: ColorScheme
let info: CVInfo
let content: [CVInfo]
let style: CVStyle
init(info: CVInfo, style: CVStyle) {
self.info = info
init(content: [CVInfo], style: CVStyle) {
self.content = content
self.style = style
}
@ -21,19 +21,35 @@ struct ContentView: View {
@State
var didReadDarkMode = false
@State
var selectedLanguageIndex = 0
var colorStyle: ColorScheme {
darkModeEnabled ? .dark : .light
}
var info: CVInfo {
content[selectedLanguageIndex]
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Picker("", selection: $selectedLanguageIndex) {
ForEach(Array(content.enumerated()), id: \.element) { content in
Text(content.element.language)
.tag(content.offset)
}
}.frame(width: 100)
Button(action: createAndSavePDF) {
Label("Save", systemSymbol: .squareAndArrowUp)
Label("Export PDF", systemSymbol: .squareAndArrowDown)
}
.padding()
Spacer()
Toggle("Dark mode", isOn: $darkModeEnabled)
.toggleStyle(SwitchToggleStyle())
}
.padding(.horizontal)
ScrollView(.vertical) {
CV(info: info, style: style)
}.frame(width: style.pageWidth)
@ -89,7 +105,7 @@ struct ContentView: View {
}
}
private var content: some View {
private var renderContent: some View {
CV(info: info, style: style)
.frame(width: style.pageWidth, height: style.pageHeight)
}
@ -97,7 +113,7 @@ struct ContentView: View {
@MainActor
private func renderPDF() -> URL? {
let pdfURL = URL.documentsDirectory.appending(path: "cv.pdf")
let renderer = ImageRenderer(content: content)
let renderer = ImageRenderer(content: renderContent)
var didFinish = false
renderer.render { size, context in
@ -128,7 +144,7 @@ struct ContentView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(info: cvInfo, style: cvStyle)
ContentView(content: [cvInfoEnglish, cvInfoGerman], style: cvStyle)
.frame(width: 600, height: 600 * sqrt(2))
}
}

View File

@ -20,12 +20,13 @@ let cvStyle = CVStyle(
iconSize: 20,
rowSpacing: 3,
verticalTagSpacing: 3,
horizontalGap: 5,
horizontalGap: 3,
tagBackground: .gray.opacity(0.1),
tagRounding: 8)
)
let cvInfo = CVInfo(
let cvInfoEnglish = CVInfo(
language: "English",
top: TopInfo(
imageName: "Cover",
name: "Christoph Hagen",
@ -114,7 +115,96 @@ let cvInfo = CVInfo(
"I'm interested in acquiring knowledge and new skills, developing cutting-edge technologies, and finding efficient solutions.",
"I usually work on various creative projects, including woodworking, electronics, sewing, and programming. I also love being active in nature."
]),
footer: [
"Design by Christoph Hagen, 2023.",
"Please use the information in this document responsibly. Consider the environmental impact before printing."
])
footer: "Design by Christoph Hagen, 2023.")
let cvInfoGerman = CVInfo(
language: "German",
top: TopInfo(
imageName: "Cover",
name: "Christoph Hagen",
tagLine: "Problemlöser und kreativer Kopf mit einer Vorliebe für interdisziplinäre Arbeit.",
place: "Würzburg",
ageText: "32 Jahre",
web: "christophhagen.de",
email: "jobs@christophhagen.de",
phone: "Auf Anfrage",
github: "github.com/christophhagen"),
work: .init(title: "Berufserfahrung", items: [
CareerStation(
time: "Jul 2020 - Jul 2023",
location: "Braunschweig",
title: "Deutsches Zentrum für Luft- und Raumfahrt",
subtitle: "Systemingenieur",
text: "Verantwortlich für die Flugzeugsysteme und Avionik, Sicherheitsanalysen, und Software einer hochfliegenden Solardrohne."),
CareerStation(
time: "Mär 2018 - Dez 2019",
location: "Würzburg",
title: "Julius-Maximilians-Universität",
subtitle: "Wissenschaftlicher Mitarbeiter",
text: "Forschung an Privatsphäre- und IT-Sicherheitstechnologien in der Gruppe Secure Software Systems."),
CareerStation(
time: "Jul 2017 - Okt 2017",
location: "Tokio, Japan",
title: "National Institute of Informatics",
subtitle: "Research Intern (Intelligent Robotics)",
text: "Thema: Concept Acquisition through interactions between Humans and Robots"),
CareerStation(
time: "Sep 2014 - Nov 2016",
location: "Würzburg",
title: "Julius-Maximilians-Universität",
subtitle: "Research & Teaching Assistant",
text: "Leitung von Übungen und Robotikworkshops, Entwurf einer modularen Verbindung für einen Roboterarm.")
]),
education: .init(title: "Bildung", items: [
CareerStation(
time: "Okt 2015 - Sep 2017",
location: "Kiruna, Schweden",
title: "Luleå University of Technology",
subtitle: "M. Sc. in Space Technology",
text: "Erasmus Mundus Double Degree Master mit Kursen in Robotik, Satellitenentwicklung und -kontrolle, Atmosphären- und Weltraumphsyik."),
CareerStation(
time: "Okt 2015 - Sep 2017",
location: "Espoo, Finnland",
title: "Aalto University of Electrical Engineering",
subtitle: "M. Sc. in Space Robotics and Automation",
text: "Abschlussarbeit: A Bluetooth based intra-satellite communication system"),
CareerStation(
time: "Okt 2013 - Aug 2015",
location: "Würzburg",
title: "Julius-Maximilians-Universität",
subtitle: "B. Sc. in Luft- und Raumfahrtinformatik",
text: "Mobile Robotik, Satellitensysteme, Echtzeitbetriebssysteme, Mathematik and Physik.")
]),
publications: .init(title: "Publikationen", items: [
Publication(
venue: "33rd Anual INCOSE International Symposium 2023",
title: "Model Based Verification and Validation Planning for a Solar Powered High-Altitude Platform"),
Publication(
venue: "ACM Transactions on Privacy and Security 2022",
title: "Contact Discovery in Mobile Messengers: Low-cost Attacks, Quantitative Analyses, and Efficient Mitigations"),
Publication(
venue: "Network and Distributed Systems Symposium 2021",
title: "All the Numbers are US: Large-scale Abuse of Contact Discovery in Mobile Messengers")
]),
skills: .init(title: "Fähigkeiten", items: [
SkillsSet(
systemSymbol: .characterBubble,
entries: ["Deutsch", "Englisch"]),
SkillsSet(
systemSymbol: .keyboard,
entries: ["Swift", "C", "C++", "Python"]),
SkillsSet(
systemSymbol: .display2,
entries: ["iOS", "Embedded", "macOS", "Linux"]),
SkillsSet(
systemSymbol: .theatermaskAndPaintbrush,
entries: ["UI Design", "CAD", "Holzverarbeitung", "Elektronik", "Foto/Videobearbeitung"]),
SkillsSet(
systemSymbol: .personFillCheckmark,
entries: ["Problemlösung", "Analytisches Denken", "Entscheidungsfindung", "Optimierung"])
]),
about: .init(title: "Über mich", items: [
"Ich eigne mir gerne neues Wissen und Fähigkeiten an, mag die Arbeit an Zukunftstechnologien, und schätze effizente Lösungen für Probleme.",
"Ich arbeite oft an verschiedenen Kreativprojekten, unter anderem Möbelbau, Elektronik, Software, oder Näharbeiten. Außerdem bin ich sehr gerne sportlich in der Natur aktiv."
]),
footer: "Design by Christoph Hagen, 2023.")

View File

@ -9,6 +9,8 @@ struct Titled<Content> {
struct CVInfo {
let language: String
let top: TopInfo
let work: Titled<CareerStation>
@ -21,5 +23,27 @@ struct CVInfo {
let about: Titled<String>
let footer: [String]
let footer: String
}
extension CVInfo: Identifiable {
var id: String {
language
}
}
extension CVInfo: Equatable {
static func == (lhs: CVInfo, rhs: CVInfo) -> Bool {
lhs.language == rhs.language
}
}
extension CVInfo: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(language)
}
}

View File

@ -29,7 +29,7 @@ struct TitledIconSection: View {
TitledSection(title: content.title, spacing: titleSpacing) {
VStack(alignment: .leading) {
ForEach(content.items) { item in
HStack(alignment: .firstTextBaseline) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
Image(systemSymbol: item.systemSymbol)
.frame(
width: style.iconSize,

View File

@ -24,6 +24,7 @@ struct TopView: View {
RightImageLabel(info.place, systemSymbol: .house)
.padding(.leading, -4)
RightImageLabel(info.ageText, systemSymbol: .hourglass)
Spacer()
}.font(.subheadline)
}
.frame(width: sideWidth)
@ -63,7 +64,7 @@ struct TopView_Previews: PreviewProvider {
TopView(info: .init(
imageName: "Cover",
name: "Christoph Hagen",
tagLine: "Problem solver and creative mind with a favour for interdisciplinary work.",
tagLine: "Problem solver with a favour for interdisciplinary work.",
place: "Würzburg, Germany",
ageText: "Age 32",
web: "christophhagen.de",

View File

@ -4,7 +4,7 @@ import SwiftUI
struct ResumeBuilderApp: App {
var body: some Scene {
WindowGroup {
ContentView(info: cvInfo, style: cvStyle)
}
ContentView(content: [cvInfoEnglish, cvInfoGerman], style: cvStyle)
}.windowResizability(.contentSize)
}
}