Add multi-language option
This commit is contained in:
parent
55de8ada91
commit
911fc0c8f8
@ -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)))
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user