First version

This commit is contained in:
Christoph Hagen 2023-08-18 22:47:24 +02:00
parent 1d6e36e2de
commit bd87a4fb6f
48 changed files with 1453 additions and 30 deletions

BIN
Icon.key Executable file

Binary file not shown.

View File

@ -8,18 +8,69 @@
/* Begin PBXBuildFile section */
E267D1742A8E0DE80069112B /* ResumeBuilderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1732A8E0DE80069112B /* ResumeBuilderApp.swift */; };
E267D1762A8E0DE80069112B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1752A8E0DE80069112B /* ContentView.swift */; };
E267D1762A8E0DE80069112B /* CV.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1752A8E0DE80069112B /* CV.swift */; };
E267D1782A8E0DE90069112B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E267D1772A8E0DE90069112B /* Assets.xcassets */; };
E267D17B2A8E0DE90069112B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E267D17A2A8E0DE90069112B /* Preview Assets.xcassets */; };
E267D1832A8E0F320069112B /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1822A8E0F320069112B /* Color+Extension.swift */; };
E267D1852A8E11930069112B /* TopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1842A8E11930069112B /* TopView.swift */; };
E267D1882A8E12D60069112B /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E267D1872A8E12D60069112B /* SFSafeSymbols */; };
E267D18A2A8E140E0069112B /* LeftImageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1892A8E140E0069112B /* LeftImageLabel.swift */; };
E267D18C2A8E1A890069112B /* TopViewImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D18B2A8E1A890069112B /* TopViewImage.swift */; };
E267D18E2A8E1BEE0069112B /* RightImageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D18D2A8E1BEE0069112B /* RightImageLabel.swift */; };
E267D1902A8E32B70069112B /* CareerStationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D18F2A8E32B70069112B /* CareerStationView.swift */; };
E267D1922A8E347A0069112B /* TitledCareerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1912A8E347A0069112B /* TitledCareerSection.swift */; };
E267D1942A8E38C60069112B /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1932A8E38C60069112B /* Data.swift */; };
E267D1962A8E3E760069112B /* TitledTextSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1952A8E3E760069112B /* TitledTextSection.swift */; };
E267D1982A8E43580069112B /* TitledSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1972A8E43580069112B /* TitledSection.swift */; };
E267D19A2A8E44F40069112B /* TitledIconSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1992A8E44F40069112B /* TitledIconSection.swift */; };
E267D19C2A8E45470069112B /* CareerStation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D19B2A8E45470069112B /* CareerStation.swift */; };
E267D19E2A8E45540069112B /* TopInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D19D2A8E45540069112B /* TopInfo.swift */; };
E267D1A02A8E45620069112B /* CVInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D19F2A8E45620069112B /* CVInfo.swift */; };
E267D1A22A8E45AE0069112B /* SkillsSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1A12A8E45AE0069112B /* SkillsSet.swift */; };
E267D1A72A8ECC170069112B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1A62A8ECC170069112B /* ContentView.swift */; };
E267D1A92A8F5B430069112B /* FlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1A82A8F5B430069112B /* FlowLayout.swift */; };
E267D1AD2A8F694A0069112B /* PublicationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1AC2A8F694A0069112B /* PublicationView.swift */; };
E267D1AF2A8F69830069112B /* Publication.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1AE2A8F69830069112B /* Publication.swift */; };
E267D1B42A8F7FEE0069112B /* LeftBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1B32A8F7FEE0069112B /* LeftBorderView.swift */; };
E267D1B62A8F96310069112B /* CVStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1B52A8F96310069112B /* CVStyle.swift */; };
E267D1B82A8F9A2A0069112B /* HeaderStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1B72A8F9A2A0069112B /* HeaderStyle.swift */; };
E267D1BA2A8F9D9C0069112B /* SkillStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1B92A8F9D9C0069112B /* SkillStyle.swift */; };
E267D1BC2A8FFF300069112B /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1BB2A8FFF300069112B /* TagView.swift */; };
E267D1C02A9009780069112B /* CVLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1BF2A9009780069112B /* CVLanguage.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
E267D1702A8E0DE80069112B /* ResumeBuilder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ResumeBuilder.app; sourceTree = BUILT_PRODUCTS_DIR; };
E267D1732A8E0DE80069112B /* ResumeBuilderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeBuilderApp.swift; sourceTree = "<group>"; };
E267D1752A8E0DE80069112B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
E267D1752A8E0DE80069112B /* CV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CV.swift; sourceTree = "<group>"; };
E267D1772A8E0DE90069112B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
E267D17A2A8E0DE90069112B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
E267D17C2A8E0DE90069112B /* ResumeBuilder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ResumeBuilder.entitlements; sourceTree = "<group>"; };
E267D1822A8E0F320069112B /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = "<group>"; };
E267D1842A8E11930069112B /* TopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopView.swift; sourceTree = "<group>"; };
E267D1892A8E140E0069112B /* LeftImageLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftImageLabel.swift; sourceTree = "<group>"; };
E267D18B2A8E1A890069112B /* TopViewImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopViewImage.swift; sourceTree = "<group>"; };
E267D18D2A8E1BEE0069112B /* RightImageLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RightImageLabel.swift; sourceTree = "<group>"; };
E267D18F2A8E32B70069112B /* CareerStationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareerStationView.swift; sourceTree = "<group>"; };
E267D1912A8E347A0069112B /* TitledCareerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitledCareerSection.swift; sourceTree = "<group>"; };
E267D1932A8E38C60069112B /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
E267D1952A8E3E760069112B /* TitledTextSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitledTextSection.swift; sourceTree = "<group>"; };
E267D1972A8E43580069112B /* TitledSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitledSection.swift; sourceTree = "<group>"; };
E267D1992A8E44F40069112B /* TitledIconSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitledIconSection.swift; sourceTree = "<group>"; };
E267D19B2A8E45470069112B /* CareerStation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareerStation.swift; sourceTree = "<group>"; };
E267D19D2A8E45540069112B /* TopInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopInfo.swift; sourceTree = "<group>"; };
E267D19F2A8E45620069112B /* CVInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVInfo.swift; sourceTree = "<group>"; };
E267D1A12A8E45AE0069112B /* SkillsSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkillsSet.swift; sourceTree = "<group>"; };
E267D1A62A8ECC170069112B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
E267D1A82A8F5B430069112B /* FlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowLayout.swift; sourceTree = "<group>"; };
E267D1AC2A8F694A0069112B /* PublicationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicationView.swift; sourceTree = "<group>"; };
E267D1AE2A8F69830069112B /* Publication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publication.swift; sourceTree = "<group>"; };
E267D1B32A8F7FEE0069112B /* LeftBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftBorderView.swift; sourceTree = "<group>"; };
E267D1B52A8F96310069112B /* CVStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVStyle.swift; sourceTree = "<group>"; };
E267D1B72A8F9A2A0069112B /* HeaderStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderStyle.swift; sourceTree = "<group>"; };
E267D1B92A8F9D9C0069112B /* SkillStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkillStyle.swift; sourceTree = "<group>"; };
E267D1BB2A8FFF300069112B /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
E267D1BF2A9009780069112B /* CVLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVLanguage.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -27,6 +78,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E267D1882A8E12D60069112B /* SFSafeSymbols in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -53,10 +105,19 @@
isa = PBXGroup;
children = (
E267D1732A8E0DE80069112B /* ResumeBuilderApp.swift */,
E267D1752A8E0DE80069112B /* ContentView.swift */,
E267D1A62A8ECC170069112B /* ContentView.swift */,
E267D1752A8E0DE80069112B /* CV.swift */,
E267D1B02A8F6E5B0069112B /* Main Elements */,
E267D1B22A8F6E9C0069112B /* Elements */,
E267D1B12A8F6E7F0069112B /* Generic Elements */,
E267D1772A8E0DE90069112B /* Assets.xcassets */,
E267D17C2A8E0DE90069112B /* ResumeBuilder.entitlements */,
E267D1792A8E0DE90069112B /* Preview Content */,
E267D1822A8E0F320069112B /* Color+Extension.swift */,
E267D1932A8E38C60069112B /* Data.swift */,
E267D1A52A8EC34B0069112B /* Data */,
E267D1BE2A90095D0069112B /* Language */,
E267D1BD2A9009390069112B /* Style */,
);
path = ResumeBuilder;
sourceTree = "<group>";
@ -69,6 +130,70 @@
path = "Preview Content";
sourceTree = "<group>";
};
E267D1A52A8EC34B0069112B /* Data */ = {
isa = PBXGroup;
children = (
E267D19F2A8E45620069112B /* CVInfo.swift */,
E267D19B2A8E45470069112B /* CareerStation.swift */,
E267D1A12A8E45AE0069112B /* SkillsSet.swift */,
E267D19D2A8E45540069112B /* TopInfo.swift */,
E267D1AE2A8F69830069112B /* Publication.swift */,
);
path = Data;
sourceTree = "<group>";
};
E267D1B02A8F6E5B0069112B /* Main Elements */ = {
isa = PBXGroup;
children = (
E267D1842A8E11930069112B /* TopView.swift */,
E267D1912A8E347A0069112B /* TitledCareerSection.swift */,
E267D1992A8E44F40069112B /* TitledIconSection.swift */,
E267D1952A8E3E760069112B /* TitledTextSection.swift */,
);
path = "Main Elements";
sourceTree = "<group>";
};
E267D1B12A8F6E7F0069112B /* Generic Elements */ = {
isa = PBXGroup;
children = (
E267D1892A8E140E0069112B /* LeftImageLabel.swift */,
E267D18D2A8E1BEE0069112B /* RightImageLabel.swift */,
E267D1A82A8F5B430069112B /* FlowLayout.swift */,
E267D1972A8E43580069112B /* TitledSection.swift */,
E267D1B32A8F7FEE0069112B /* LeftBorderView.swift */,
E267D1BB2A8FFF300069112B /* TagView.swift */,
);
path = "Generic Elements";
sourceTree = "<group>";
};
E267D1B22A8F6E9C0069112B /* Elements */ = {
isa = PBXGroup;
children = (
E267D18B2A8E1A890069112B /* TopViewImage.swift */,
E267D1AC2A8F694A0069112B /* PublicationView.swift */,
E267D18F2A8E32B70069112B /* CareerStationView.swift */,
);
path = Elements;
sourceTree = "<group>";
};
E267D1BD2A9009390069112B /* Style */ = {
isa = PBXGroup;
children = (
E267D1B52A8F96310069112B /* CVStyle.swift */,
E267D1B72A8F9A2A0069112B /* HeaderStyle.swift */,
E267D1B92A8F9D9C0069112B /* SkillStyle.swift */,
);
path = Style;
sourceTree = "<group>";
};
E267D1BE2A90095D0069112B /* Language */ = {
isa = PBXGroup;
children = (
E267D1BF2A9009780069112B /* CVLanguage.swift */,
);
path = Language;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -85,6 +210,9 @@
dependencies = (
);
name = ResumeBuilder;
packageProductDependencies = (
E267D1872A8E12D60069112B /* SFSafeSymbols */,
);
productName = ResumeBuilder;
productReference = E267D1702A8E0DE80069112B /* ResumeBuilder.app */;
productType = "com.apple.product-type.application";
@ -113,6 +241,9 @@
Base,
);
mainGroup = E267D1672A8E0DE80069112B;
packageReferences = (
E267D1862A8E12D60069112B /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
);
productRefGroup = E267D1712A8E0DE80069112B /* Products */;
projectDirPath = "";
projectRoot = "";
@ -139,7 +270,32 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E267D1762A8E0DE80069112B /* ContentView.swift in Sources */,
E267D1902A8E32B70069112B /* CareerStationView.swift in Sources */,
E267D1982A8E43580069112B /* TitledSection.swift in Sources */,
E267D1942A8E38C60069112B /* Data.swift in Sources */,
E267D19E2A8E45540069112B /* TopInfo.swift in Sources */,
E267D1922A8E347A0069112B /* TitledCareerSection.swift in Sources */,
E267D1B42A8F7FEE0069112B /* LeftBorderView.swift in Sources */,
E267D1B62A8F96310069112B /* CVStyle.swift in Sources */,
E267D1C02A9009780069112B /* CVLanguage.swift in Sources */,
E267D1762A8E0DE80069112B /* CV.swift in Sources */,
E267D1A22A8E45AE0069112B /* SkillsSet.swift in Sources */,
E267D1A72A8ECC170069112B /* ContentView.swift in Sources */,
E267D18E2A8E1BEE0069112B /* RightImageLabel.swift in Sources */,
E267D1962A8E3E760069112B /* TitledTextSection.swift in Sources */,
E267D1B82A8F9A2A0069112B /* HeaderStyle.swift in Sources */,
E267D18A2A8E140E0069112B /* LeftImageLabel.swift in Sources */,
E267D19A2A8E44F40069112B /* TitledIconSection.swift in Sources */,
E267D18C2A8E1A890069112B /* TopViewImage.swift in Sources */,
E267D19C2A8E45470069112B /* CareerStation.swift in Sources */,
E267D1BA2A8F9D9C0069112B /* SkillStyle.swift in Sources */,
E267D1AD2A8F694A0069112B /* PublicationView.swift in Sources */,
E267D1BC2A8FFF300069112B /* TagView.swift in Sources */,
E267D1A02A8E45620069112B /* CVInfo.swift in Sources */,
E267D1832A8E0F320069112B /* Color+Extension.swift in Sources */,
E267D1AF2A8F69830069112B /* Publication.swift in Sources */,
E267D1852A8E11930069112B /* TopView.swift in Sources */,
E267D1A92A8F5B430069112B /* FlowLayout.swift in Sources */,
E267D1742A8E0DE80069112B /* ResumeBuilderApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -274,6 +430,8 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = CHResume;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -301,6 +459,8 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = CHResume;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -336,6 +496,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
E267D1862A8E12D60069112B /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
E267D1872A8E12D60069112B /* SFSafeSymbols */ = {
isa = XCSwiftPackageProductDependency;
package = E267D1862A8E12D60069112B /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;
productName = SFSafeSymbols;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = E267D1682A8E0DE80069112B /* Project object */;
}

View File

@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "sfsafesymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
"state" : {
"revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
"version" : "4.1.1"
}
}
],
"version" : 2
}

View File

@ -1,6 +1,33 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0",
"green" : "144",
"red" : "244"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0",
"green" : "144",
"red" : "244"
}
},
"idiom" : "universal"
}
],

View File

@ -1,51 +1,61 @@
{
"images" : [
{
"filename" : "icon 9.jpg",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon 8.jpg",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon 7.jpg",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon 6.jpg",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon 5.jpg",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon 4.jpg",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon 3.jpg",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon 2.jpg",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon 1.jpg",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon.jpg",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "photo.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -0,0 +1,52 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "github.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "github 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

76
ResumeBuilder/CV.swift Normal file
View File

@ -0,0 +1,76 @@
import SwiftUI
struct CV: View {
let info: CVInfo
let style: CVStyle
private var twoColumnSpacing: CGFloat {
style.columnSpacing
}
var body: some View {
VStack(alignment: .leading) {
TopView(info: info.top, style: style.header)
.frame(height: style.header.height)
Rectangle()
.fill(Color.accentColor)
.frame(height: style.header.lineWidth)
GeometryReader { geo in
let columnWidth = max(0, (geo.size.width - twoColumnSpacing)) / 2
HStack(alignment: .top, spacing: twoColumnSpacing) {
VStack(alignment: .leading) {
TitledCareerSection(
style: style.section,
content: info.work)
TitledCareerSection(
style: style.section,
content: info.education)
}.frame(width: columnWidth)
VStack(alignment: .leading) {
TitledSection(
title: info.publications.title,
spacing: style.section.titleSpacing) {
ForEach(info.publications.items) { item in
PublicationView(
info: item,
borderSpacing: style.section.borderSpacing,
borderWidth: style.section.borderWidth)
.padding(.bottom, style.section.bottomSpacing)
}
}
TitledIconSection(
content: info.skills,
titleSpacing: style.section.titleSpacing,
width: columnWidth,
style: style.skillStyle)
TitledTextSection(
content: info.about,
titleSpacing: style.section.titleSpacing,
paragraphSpacing: style.section.paragraphSpacing)
}.frame(width: columnWidth)
}
}
Spacer(minLength: 0)
HStack {
VStack(alignment: .leading) {
ForEach(info.footer) { text in
Text(text)
}
}
}
.font(.footnote)
.foregroundColor(.secondary)
}
.padding()
.aspectRatio(1 / sqrt(2), contentMode: .fit)
}
}
struct CV_Previews: PreviewProvider {
static var previews: some View {
CV(info: cvInfo, style: cvStyle)
.previewLayout(.fixed(width: 600, height: 600 * sqrt(2)))
}
}

View File

@ -0,0 +1,16 @@
import SwiftUI
extension Color {
init(_ r: Int, _ g: Int, _ b: Int) {
self.init(Double(r) / 255, Double(g) / 255, Double(b) / 255)
}
init(r: Int, g: Int, b: Int) {
self.init(Double(r) / 255, Double(g) / 255, Double(b) / 255)
}
init(_ r: Double, _ g: Double, _ b: Double) {
self.init(red: r, green: g, blue: b)
}
}

View File

@ -1,26 +1,107 @@
//
// ContentView.swift
// ResumeBuilder
//
// Created by CH on 17.08.23.
//
import SwiftUI
import SFSafeSymbols
struct ContentView: View {
let info: CVInfo
let style: CVStyle
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
VStack(alignment: .leading) {
HStack {
Button(action: createAndSavePDF) {
Label("Save", systemSymbol: .squareAndArrowUp)
}
.padding()
}
ScrollView(.vertical) {
CV(info: info, style: style)
}.frame(width: style.pageWidth)
}
}
private func createAndSavePDF() {
DispatchQueue.main.async {
guard let pdfURL = renderPDF() else {
return
}
guard let url = showSavePanel() else {
return
}
writePDF(at: pdfURL, to: url)
}
}
private func showSavePanel() -> URL? {
let savePanel = NSSavePanel()
savePanel.allowedContentTypes = [.pdf]
savePanel.canCreateDirectories = true
savePanel.isExtensionHidden = false
savePanel.allowsOtherFileTypes = false
savePanel.title = "Save PDF"
savePanel.message = "Choose a location to save a PDF of the resume"
savePanel.nameFieldLabel = "File name:"
savePanel.nameFieldStringValue = "CV.pdf"
let response = savePanel.runModal()
guard response == .OK else {
return nil
}
return savePanel.url
}
private func writePDF(at source: URL, to destination: URL) {
do {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
try FileManager.default.copyItem(at: source, to: destination)
} catch {
print("Failed to save pdf: \(error)")
}
}
private var content: some View {
CV(info: info, style: style)
.frame(width: style.pageWidth, height: style.pageHeight)
}
@MainActor
private func renderPDF() -> URL? {
let pdfURL = URL.documentsDirectory.appending(path: "cv.pdf")
let renderer = ImageRenderer(content: content)
var didFinish = false
renderer.render { size, context in
var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)
guard let pdf = CGContext(pdfURL as CFURL, mediaBox: &box, nil) else {
print("Failed to create CGContext")
return
}
let options: [CFString: Any] = [
kCGPDFContextMediaBox: CGRect(origin: .zero, size: size)
]
pdf.beginPDFPage(options as CFDictionary)
context(pdf)
pdf.endPDFPage()
pdf.closePDF()
didFinish = true
}
guard didFinish else {
return nil
}
print("PDF created")
return pdfURL
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView(info: cvInfo, style: cvStyle)
.frame(width: 600, height: 600 * sqrt(2))
}
}

120
ResumeBuilder/Data.swift Normal file
View File

@ -0,0 +1,120 @@
import Foundation
import SwiftUI
let cvStyle = CVStyle(
pageWidth: 600,
header: HeaderStyle(
height: 100,
lineWidth: 1,
iconHeight: 20,
imageShadowSize: 5,
imageBorderWidth: 2),
columnSpacing: 10,
section: .init(
titleSpacing: 10,
borderSpacing: 5,
borderWidth: 3,
bottomSpacing: 10,
paragraphSpacing: 5),
skillStyle: SkillStyle(
iconSize: 20,
rowSpacing: 3,
verticalTagSpacing: 3,
horizontalGap: 5,
tagBackground: .gray.opacity(0.1),
tagRounding: 8)
)
let cvInfo = CVInfo(
top: TopInfo(
imageName: "Cover",
name: "Christoph Hagen",
tagLine: "Problem solver and creative mind with a favour for interdisciplinary work.",
place: "Würzburg, Germany",
ageText: "Age 32",
web: "christophhagen.de",
email: "jobs@christophhagen.de",
phone: "Upon Request",
github: "github.com/christophhagen"),
work: .init(title: "Work experience", items: [
CareerStation(
time: "Jul 2020 - Jul 2023",
location: "Braunschweig, Germany",
title: "German Aerospace Center",
subtitle: "Systems engineer",
text: "Responsible for aircraft systems and avionics of a high-altitude solar drone, safety, and software."),
CareerStation(
time: "Mar 2018 - Dec 2019",
location: "Würzburg, Germany",
title: "Julius-Maximilians-Universität",
subtitle: "Research Assistant",
text: "Working on privacy and security technologies in the Secure Software Systems group."),
CareerStation(
time: "Jul 2017 - Oct 2017",
location: "Tokyo, Japan",
title: "National Institute of Informatics",
subtitle: "Research Intern (Intelligent Robotics)",
text: "Topic: Concept Acquisition through interactions between Humans and Robots"),
CareerStation(
time: "Sep 2014 - Nov 2016",
location: "Würzburg, Germany",
title: "Julius-Maximilians-Universität",
subtitle: "Research & Teaching Assistant",
text: "Teaching exercises and robotics workshops, design of a modular robot arm connector.")
]),
education: .init(title: "Education", items: [
CareerStation(
time: "Oct 2015 - Sep 2017",
location: "Kiruna, Sweden",
title: "Luleå University of Technology",
subtitle: "M. Sc. in Space Technology",
text: "Erasmus Mundus Double Degree Master with courses on robotics, satellite design and control, atmosphere and space physics."),
CareerStation(
time: "Oct 2015 - Sep 2017",
location: "Espoo, Finland",
title: "Aalto University of Electrical Engineering",
subtitle: "M. Sc. in Space Robotics and Automation",
text: "Thesis topic: A Bluetooth based intra-satellite communication system"),
CareerStation(
time: "Oct 2013 - Aug 2015",
location: "Würzburg, Germany",
title: "Julius-Maximilians-Universität",
subtitle: "B. Sc. in Aerospace Computer Science",
text: "Mobile robotics, satellite subsystems, real-time systems, mathematics and physics.")
]),
publications: .init(title: "Publications", 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: "Skills", items: [
SkillsSet(
systemSymbol: .characterBubble,
entries: ["German", "English"]),
SkillsSet(
systemSymbol: .keyboard,
entries: ["Swift", "C", "C++", "Python"]),
SkillsSet(
systemSymbol: .display2,
entries: ["iOS", "Embedded", "macOS", "Linux"]),
SkillsSet(
systemSymbol: .theatermaskAndPaintbrush,
entries: ["UI design", "CAD", "Woodworking", "Electronics", "Photo/Video editing"]),
SkillsSet(
systemSymbol: .personFillCheckmark,
entries: ["Problem solving", "Decision making", "Analytical thinking", "Optimizing"])
]),
about: .init(title: "About", items: [
"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."
])

View File

@ -0,0 +1,25 @@
import Foundation
struct Titled<Content> {
let title: String
let items: [Content]
}
struct CVInfo {
let top: TopInfo
let work: Titled<CareerStation>
let education: Titled<CareerStation>
let publications: Titled<Publication>
let skills: Titled<SkillsSet>
let about: Titled<String>
let footer: [String]
}

View File

@ -0,0 +1,18 @@
import Foundation
struct CareerStation: Identifiable {
let time: String
let location: String
let title: String
let subtitle: String?
let text: String?
var id: String {
title + time + location
}
}

View File

@ -0,0 +1,12 @@
import Foundation
struct Publication: Identifiable {
let venue: String
let title: String
var id: String {
title + venue
}
}

View File

@ -0,0 +1,13 @@
import Foundation
import SFSafeSymbols
struct SkillsSet: Identifiable {
let systemSymbol: SFSymbol
let entries: [String]
var id: String {
entries.joined()
}
}

View File

@ -0,0 +1,22 @@
import Foundation
struct TopInfo {
let imageName: String
let name: String
let tagLine: String
let place: String
let ageText: String
let web: String
let email: String
let phone: String
let github: String
}

View File

@ -0,0 +1,53 @@
import SwiftUI
import SFSafeSymbols
struct CareerStationView: View {
let info: CareerStation
let borderSpacing: CGFloat
let borderWidth: CGFloat
var body: some View {
LeftBorderView(color: .accentColor, spacing: borderSpacing, borderWidth: borderWidth) {
VStack(alignment: .leading) {
HStack {
RightImageLabel(info.time, systemSymbol: .calendar)
.padding(.leading, -4)
Spacer()
RightImageLabel(info.location, systemSymbol: .pin)
}
.font(.caption)
.foregroundColor(.secondary)
Text(info.title)
.font(.headline)
.fontWeight(.regular)
if let subtitle = info.subtitle {
Text(subtitle)
.font(.subheadline)
.foregroundColor(.accentColor)
}
if let text = info.text {
Text(text)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
}
struct CareerStationView_Previews: PreviewProvider {
static var previews: some View {
CareerStationView(info: .init(
time: "Jul 2020 - Jul 2023",
location: "Braunschweig, Germany",
title: "German Aerospace Center",
subtitle: "Systems engineer",
text: "Responsible for aircraft systems and avionics of a high-altitude solar drone, safety, and software."),
borderSpacing: 5,
borderWidth: 3)
.previewLayout(.fixed(width: 300, height: 100))
}
}

View File

@ -0,0 +1,33 @@
import SwiftUI
struct PublicationView: View {
let info: Publication
let borderSpacing: CGFloat
let borderWidth: CGFloat
var body: some View {
LeftBorderView(color: .accentColor, spacing: borderSpacing, borderWidth: borderWidth) {
VStack(alignment: .leading) {
Text(info.venue)
.font(.caption)
.foregroundColor(.secondary)
Text(info.title)
.font(.subheadline)
.fontWeight(.regular)
}
}
}
}
struct PublicationView_Previews: PreviewProvider {
static var previews: some View {
PublicationView(info: .init(
venue: "My venue",
title: "The publication title"),
borderSpacing: 5,
borderWidth: 3)
}
}

View File

@ -0,0 +1,32 @@
import SwiftUI
struct TopViewImage: View {
let image: String
let shadow: CGFloat
let lineWidth: CGFloat
var body: some View {
Image(image)
.resizable()
.aspectRatio(1, contentMode: .fit)
.clipShape(Circle())
.overlay {
Circle().stroke(.gray, lineWidth: lineWidth)
}
.shadow(radius: shadow)
.padding(lineWidth)
}
}
struct TopViewImage_Previews: PreviewProvider {
static var previews: some View {
TopViewImage(
image: "Cover",
shadow: 5,
lineWidth: 2)
.previewLayout(.fixed(width: 150, height: 150))
}
}

View File

@ -0,0 +1,145 @@
import SwiftUI
@available(iOS 16.0, *)
struct FlowLayout: Layout {
var alignment: Alignment = .center
var spacing: CGFloat?
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize {
let result = FlowResult(
in: proposal.replacingUnspecifiedDimensions().width,
subviews: subviews,
alignment: alignment,
spacing: spacing
)
return result.bounds
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) {
let result = FlowResult(
in: proposal.replacingUnspecifiedDimensions().width,
subviews: subviews,
alignment: alignment,
spacing: spacing
)
for row in result.rows {
let rowXOffset = (bounds.width - row.frame.width) * alignment.horizontal.percent
for index in row.range {
let xPos = rowXOffset + row.frame.minX + row.xOffsets[index - row.range.lowerBound] + bounds.minX
let rowYAlignment = (row.frame.height - subviews[index].sizeThatFits(.unspecified).height) *
alignment.vertical.percent
let yPos = row.frame.minY + rowYAlignment + bounds.minY
subviews[index].place(at: CGPoint(x: xPos, y: yPos), anchor: .topLeading, proposal: .unspecified)
}
}
}
struct FlowResult {
var bounds = CGSize.zero
var rows = [Row]()
struct Row {
var range: Range<Int>
var xOffsets: [Double]
var frame: CGRect
}
init(in maxPossibleWidth: Double, subviews: Subviews, alignment: Alignment, spacing: CGFloat?) {
var itemsInRow = 0
var remainingWidth = maxPossibleWidth.isFinite ? maxPossibleWidth : .greatestFiniteMagnitude
var rowMinY = 0.0
var rowHeight = 0.0
var xOffsets: [Double] = []
for (index, subview) in zip(subviews.indices, subviews) {
let idealSize = subview.sizeThatFits(.unspecified)
if index != 0 && widthInRow(index: index, idealWidth: idealSize.width) > remainingWidth {
// Finish the current row without this subview.
finalizeRow(index: max(index - 1, 0), idealSize: idealSize)
}
addToRow(index: index, idealSize: idealSize)
if index == subviews.count - 1 {
// Finish this row; it's either full or we're on the last view anyway.
finalizeRow(index: index, idealSize: idealSize)
}
}
func spacingBefore(index: Int) -> Double {
guard itemsInRow > 0 else { return 0 }
return spacing ?? subviews[index - 1].spacing.distance(to: subviews[index].spacing, along: .horizontal)
}
func widthInRow(index: Int, idealWidth: Double) -> Double {
idealWidth + spacingBefore(index: index)
}
func addToRow(index: Int, idealSize: CGSize) {
let width = widthInRow(index: index, idealWidth: idealSize.width)
xOffsets.append(maxPossibleWidth - remainingWidth + spacingBefore(index: index))
// Allocate width to this item (and spacing).
remainingWidth -= width
// Ensure the row height is as tall as the tallest item.
rowHeight = max(rowHeight, idealSize.height)
// Can fit in this row, add it.
itemsInRow += 1
}
func finalizeRow(index: Int, idealSize: CGSize) {
let rowWidth = maxPossibleWidth - remainingWidth
rows.append(
Row(
range: index - max(itemsInRow - 1, 0) ..< index + 1,
xOffsets: xOffsets,
frame: CGRect(x: 0, y: rowMinY, width: rowWidth, height: rowHeight)
)
)
bounds.width = max(bounds.width, rowWidth)
let ySpacing = spacing ?? ViewSpacing().distance(to: ViewSpacing(), along: .vertical)
bounds.height += rowHeight + (rows.count > 1 ? ySpacing : 0)
rowMinY += rowHeight + ySpacing
itemsInRow = 0
rowHeight = 0
xOffsets.removeAll()
remainingWidth = maxPossibleWidth
}
}
}
}
private extension HorizontalAlignment {
var percent: Double {
switch self {
case .leading: return 0
case .trailing: return 1
default: return 0.5
}
}
}
private extension VerticalAlignment {
var percent: Double {
switch self {
case .top: return 0
case .bottom: return 1
default: return 0.5
}
}
}
struct FlowLayout_Previews: PreviewProvider {
static var previews: some View {
FlowLayout(alignment: .leading) {
ForEach(["Swift", "C", "C++", "Python"]) { tag in
Text(tag)
.fontWeight(.light)
.padding(.horizontal, 8)
.padding(.vertical, 3)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.1))
)
}
}
.previewLayout(.fixed(width: 200, height: 150))
}
}

View File

@ -0,0 +1,35 @@
import SwiftUI
struct LeftBorderView<Content>: View where Content: View {
let color: Color
let spacing: CGFloat
let borderWidth: CGFloat
private let content: Content
init(color: Color, spacing: CGFloat, borderWidth: CGFloat, @ViewBuilder content: () -> Content) {
self.color = color
self.spacing = spacing
self.borderWidth = borderWidth
self.content = content()
}
var body: some View {
HStack(spacing: spacing) {
Rectangle()
.fill(color)
.frame(width: borderWidth)
content
}.fixedSize(horizontal: false, vertical: true)
}
}
struct LeftBorderView_Previews: PreviewProvider {
static var previews: some View {
LeftBorderView(color: .orange, spacing: 5, borderWidth: 3) {
Text("Some")
}
}
}

View File

@ -0,0 +1,28 @@
import SwiftUI
import SFSafeSymbols
struct LeftImageLabel: View {
let text: String
let systemSymbol: SFSymbol
init(_ text: String, systemSymbol: SFSymbol) {
self.text = text
self.systemSymbol = systemSymbol
}
var body: some View {
HStack(spacing: 0) {
Text(text)
Image(systemSymbol: systemSymbol)
.frame(width: 20)
}
}
}
struct LeftImageLabel_Previews: PreviewProvider {
static var previews: some View {
LeftImageLabel("Home address", systemSymbol: .house)
}
}

View File

@ -0,0 +1,28 @@
import SwiftUI
import SFSafeSymbols
struct RightImageLabel: View {
let text: String
let systemSymbol: SFSymbol
init(_ text: String, systemSymbol: SFSymbol) {
self.text = text
self.systemSymbol = systemSymbol
}
var body: some View {
HStack(spacing: 0) {
Image(systemSymbol: systemSymbol)
.frame(width: 20)
Text(text)
}
}
}
struct RightImageLabel_Previews: PreviewProvider {
static var previews: some View {
RightImageLabel("Home address", systemSymbol: .house)
}
}

View File

@ -0,0 +1,33 @@
import SwiftUI
struct TagView: View {
let text: String
let rounding: CGFloat
let color: Color
init(_ text: String, rounding: CGFloat, color: Color) {
self.text = text
self.rounding = rounding
self.color = color
}
var body: some View {
Text(text)
.fontWeight(.light)
.padding(.horizontal, rounding)
.padding(.vertical, 3)
.background(
RoundedRectangle(cornerRadius: rounding)
.fill(color)
)
}
}
struct TagView_Previews: PreviewProvider {
static var previews: some View {
TagView("Text", rounding: 8, color: .gray)
}
}

View File

@ -0,0 +1,34 @@
import SwiftUI
struct TitledSection<Content>: View where Content: View {
private let content: Content
private let title: String
private let spacing: CGFloat
init(title: String, spacing: CGFloat, @ViewBuilder content: () -> Content) {
self.title = title
self.spacing = spacing
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(title)
.font(.title)
.fontWeight(.light)
.padding(.bottom, spacing)
content
}
}
}
struct TitledSection_Previews: PreviewProvider {
static var previews: some View {
TitledSection(title: "Title", spacing: 10) {
Text("Some more text")
}
}
}

View File

@ -0,0 +1,14 @@
import Foundation
struct Titles {
let work: String
let educationTitle: String
let publicationTitle: String
let skillsTitle: String
let aboutTitle: String
}

View File

@ -0,0 +1,45 @@
import SwiftUI
struct TitledCareerSection: View {
let style: CVStyle.Section
let content: Titled<CareerStation>
var body: some View {
TitledSection(title: content.title, spacing: style.titleSpacing) {
ForEach(content.items) { item in
CareerStationView(
info: item,
borderSpacing: style.borderSpacing,
borderWidth: style.borderWidth)
.padding(.bottom, style.bottomSpacing)
}
}
}
}
struct TitledItemSection_Previews: PreviewProvider {
static var previews: some View {
TitledCareerSection(
style: .init(),
content: .init(
title: "Work experience",
items: [
.init(
time: "Jul 2020 - Jul 2023",
location: "Braunschweig, Germany",
title: "German Aerospace Center",
subtitle: "Systems engineer",
text: "Responsible for aircraft systems and avionics of a high-altitude solar drone, safety, and software."),
.init(
time: "Jul 2020 - Jul 2023",
location: "Braunschweig, Germany",
title: "German Aerospace Center",
subtitle: "Systems engineer",
text: "Responsible for aircraft systems and avionics of a high-altitude solar drone, safety, and software.")
])
)
.previewLayout(.fixed(width: 350, height: 400))
}
}

View File

@ -0,0 +1,63 @@
import SwiftUI
extension String: Identifiable {
public var id: String {
self
}
}
extension View {
public func addBorder<S>(_ content: S, width: CGFloat = 1, cornerRadius: CGFloat) -> some View where S : ShapeStyle {
let roundedRect = RoundedRectangle(cornerRadius: cornerRadius)
return clipShape(roundedRect)
.overlay(roundedRect.strokeBorder(content, lineWidth: width))
}
}
struct TitledIconSection: View {
let content: Titled<SkillsSet>
let titleSpacing: CGFloat
let width: CGFloat
let style: SkillStyle
var body: some View {
TitledSection(title: content.title, spacing: titleSpacing) {
VStack(alignment: .leading) {
ForEach(content.items) { item in
HStack(alignment: .firstTextBaseline) {
Image(systemSymbol: item.systemSymbol)
.frame(
width: style.iconSize,
height: style.iconSize)
.padding(.leading, style.horizontalGap)
FlowLayout(alignment: .leading, spacing: style.verticalTagSpacing) {
ForEach(item.entries) { tag in
TagView(
tag,
rounding: style.tagRounding,
color: style.tagBackground)
}
}
}.padding(.bottom, style.rowSpacing)
}
}
}
}
}
struct TitledIconSection_Previews: PreviewProvider {
static var previews: some View {
TitledIconSection(
content: .init(title: "Title", items: [
.init(systemSymbol: .keyboard, entries: ["Swift", "C", "C++", "Python"])
]),
titleSpacing: 10,
width: 200, style: SkillStyle())
.previewLayout(.fixed(width: 230, height: 300))
}
}

View File

@ -0,0 +1,32 @@
import SwiftUI
struct TitledTextSection: View {
let content: Titled<String>
let titleSpacing: CGFloat
let paragraphSpacing: CGFloat
var body: some View {
TitledSection(title: content.title, spacing: titleSpacing) {
ForEach(content.items) { text in
Text(text)
.font(.body)
.fontWeight(.light)
.padding(.bottom, paragraphSpacing)
}
}
}
}
struct TitledTextSection_Previews: PreviewProvider {
static var previews: some View {
TitledTextSection(
content: .init(
title: "Title",
items: ["Some longer or shorter text to explain some feature."]),
titleSpacing: 10,
paragraphSpacing: 5)
}
}

View File

@ -0,0 +1,76 @@
import SwiftUI
import SFSafeSymbols
struct TopView: View {
let info: TopInfo
let style: HeaderStyle
var body: some View {
GeometryReader { geo in
let sideWidth = max(0, (geo.size.width - geo.size.height) / 2)
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
Text(info.name)
.font(.title)
.foregroundColor(.accentColor)
Spacer(minLength: 0)
Text(info.tagLine)
.font(.subheadline)
.padding(.trailing, style.imageShadowSize)
Spacer(minLength: 0)
HStack {
RightImageLabel(info.place, systemSymbol: .house)
.padding(.leading, -4)
RightImageLabel(info.ageText, systemSymbol: .hourglass)
}.font(.subheadline)
}
.frame(width: sideWidth)
TopViewImage(
image: info.imageName,
shadow: style.imageShadowSize,
lineWidth: style.imageBorderWidth)
VStack(alignment: .trailing) {
LeftImageLabel(info.web, systemSymbol: .globe)
.frame(maxHeight: style.iconHeight)
Spacer()
LeftImageLabel(info.email, systemSymbol: .envelope)
.frame(maxHeight: style.iconHeight)
Spacer()
LeftImageLabel(info.phone, systemSymbol: .phone)
.frame(maxHeight: style.iconHeight)
Spacer()
HStack(spacing: 0) {
Spacer()
Text(info.github)
Image("Github")
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.padding(2)
.frame(width: style.iconHeight)
}.frame(maxHeight: style.iconHeight)
}
.font(.subheadline)
.frame(width: sideWidth)
}
}
}
}
struct TopView_Previews: PreviewProvider {
static var previews: some View {
TopView(info: .init(
imageName: "Cover",
name: "Christoph Hagen",
tagLine: "Problem solver and creative mind with a favour for interdisciplinary work.",
place: "Würzburg, Germany",
ageText: "Age 32",
web: "christophhagen.de",
email: "jobs@christophhagen.de",
phone: "Upon Request",
github: "github.com/christophhagen"),
style: HeaderStyle())
.previewLayout(.fixed(width: 540, height: 120))
}
}

View File

@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>

View File

@ -1,17 +1,10 @@
//
// ResumeBuilderApp.swift
// ResumeBuilder
//
// Created by CH on 17.08.23.
//
import SwiftUI
@main
struct ResumeBuilderApp: App {
var body: some Scene {
WindowGroup {
ContentView()
ContentView(info: cvInfo, style: cvStyle)
}
}
}

View File

@ -0,0 +1,40 @@
import Foundation
import SwiftUI
struct CVStyle {
struct Section {
let titleSpacing: CGFloat
let borderSpacing: CGFloat
let borderWidth: CGFloat
let bottomSpacing: CGFloat
let paragraphSpacing: CGFloat
init(titleSpacing: CGFloat = 10, borderSpacing: CGFloat = 5, borderWidth: CGFloat = 3, bottomSpacing: CGFloat = 10, paragraphSpacing: CGFloat = 5) {
self.titleSpacing = titleSpacing
self.borderSpacing = borderSpacing
self.borderWidth = borderWidth
self.bottomSpacing = bottomSpacing
self.paragraphSpacing = paragraphSpacing
}
}
let pageWidth: CGFloat
let header: HeaderStyle
let columnSpacing: CGFloat
let section: Section
let skillStyle: SkillStyle
var pageHeight: CGFloat {
pageWidth * sqrt(2)
}
}

View File

@ -0,0 +1,26 @@
import Foundation
struct HeaderStyle {
/// The total height of the header
let height: CGFloat
/// The width of the line beneath the header
let lineWidth: CGFloat
/// The height of the icons
let iconHeight: CGFloat
/// The size of the shadow around the center image
let imageShadowSize: CGFloat
let imageBorderWidth: CGFloat
init(height: CGFloat = 100, lineWidth: CGFloat = 1, iconHeight: CGFloat = 20, imageShadowSize: CGFloat = 5, imageBorderWidth: CGFloat = 2) {
self.height = height
self.lineWidth = lineWidth
self.iconHeight = iconHeight
self.imageShadowSize = imageShadowSize
self.imageBorderWidth = imageBorderWidth
}
}

View File

@ -0,0 +1,27 @@
import Foundation
import SwiftUI
struct SkillStyle {
let iconSize: CGFloat
let rowSpacing: CGFloat
let verticalTagSpacing: CGFloat
let horizontalGap: CGFloat
let tagBackground: Color
let tagRounding: CGFloat
init(iconSize: CGFloat = 20, rowSpacing: CGFloat = 3, verticalTagSpacing: CGFloat = 3, horizontalGap: CGFloat = 5, tagBackground: Color = .gray, tagRounding: CGFloat = 8) {
self.iconSize = iconSize
self.rowSpacing = rowSpacing
self.verticalTagSpacing = verticalTagSpacing
self.horizontalGap = horizontalGap
self.tagBackground = tagBackground
self.tagRounding = tagRounding
}
}