First version
@ -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 */;
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "sfsafesymbols",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
|
||||
"state" : {
|
||||
"revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
|
||||
"version" : "4.1.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
@ -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"
|
||||
}
|
||||
],
|
||||
|
@ -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"
|
||||
|
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 1.jpg
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 2.jpg
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 3.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 4.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 5.jpg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 6.jpg
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 7.jpg
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 8.jpg
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon 9.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
ResumeBuilder/Assets.xcassets/AppIcon.appiconset/icon.jpg
Normal file
After Width: | Height: | Size: 377 KiB |
21
ResumeBuilder/Assets.xcassets/Cover.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
ResumeBuilder/Assets.xcassets/Cover.imageset/photo.jpg
vendored
Normal file
After Width: | Height: | Size: 2.5 MiB |
52
ResumeBuilder/Assets.xcassets/Github.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
ResumeBuilder/Assets.xcassets/Github.imageset/github 1.png
vendored
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
ResumeBuilder/Assets.xcassets/Github.imageset/github.png
vendored
Normal file
After Width: | Height: | Size: 23 KiB |
76
ResumeBuilder/CV.swift
Normal 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)))
|
||||
}
|
||||
}
|
16
ResumeBuilder/Color+Extension.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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
@ -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."
|
||||
])
|
25
ResumeBuilder/Data/CVInfo.swift
Normal 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]
|
||||
}
|
18
ResumeBuilder/Data/CareerStation.swift
Normal 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
|
||||
}
|
||||
}
|
12
ResumeBuilder/Data/Publication.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
struct Publication: Identifiable {
|
||||
|
||||
let venue: String
|
||||
|
||||
let title: String
|
||||
|
||||
var id: String {
|
||||
title + venue
|
||||
}
|
||||
}
|
13
ResumeBuilder/Data/SkillsSet.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
import SFSafeSymbols
|
||||
|
||||
struct SkillsSet: Identifiable {
|
||||
|
||||
let systemSymbol: SFSymbol
|
||||
|
||||
let entries: [String]
|
||||
|
||||
var id: String {
|
||||
entries.joined()
|
||||
}
|
||||
}
|
22
ResumeBuilder/Data/TopInfo.swift
Normal 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
|
||||
}
|
53
ResumeBuilder/Elements/CareerStationView.swift
Normal 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))
|
||||
}
|
||||
}
|
33
ResumeBuilder/Elements/PublicationView.swift
Normal 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)
|
||||
}
|
||||
}
|
32
ResumeBuilder/Elements/TopViewImage.swift
Normal 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))
|
||||
}
|
||||
}
|
145
ResumeBuilder/Generic Elements/FlowLayout.swift
Normal 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))
|
||||
}
|
||||
}
|
35
ResumeBuilder/Generic Elements/LeftBorderView.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
28
ResumeBuilder/Generic Elements/LeftImageLabel.swift
Normal 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)
|
||||
}
|
||||
}
|
28
ResumeBuilder/Generic Elements/RightImageLabel.swift
Normal 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)
|
||||
}
|
||||
}
|
33
ResumeBuilder/Generic Elements/TagView.swift
Normal 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)
|
||||
}
|
||||
}
|
34
ResumeBuilder/Generic Elements/TitledSection.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
14
ResumeBuilder/Language/CVLanguage.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
struct Titles {
|
||||
|
||||
let work: String
|
||||
|
||||
let educationTitle: String
|
||||
|
||||
let publicationTitle: String
|
||||
|
||||
let skillsTitle: String
|
||||
|
||||
let aboutTitle: String
|
||||
}
|
45
ResumeBuilder/Main Elements/TitledCareerSection.swift
Normal 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))
|
||||
}
|
||||
}
|
63
ResumeBuilder/Main Elements/TitledIconSection.swift
Normal 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))
|
||||
}
|
||||
}
|
32
ResumeBuilder/Main Elements/TitledTextSection.swift
Normal 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)
|
||||
}
|
||||
}
|
76
ResumeBuilder/Main Elements/TopView.swift
Normal 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))
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
ResumeBuilder/Style/CVStyle.swift
Normal 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)
|
||||
}
|
||||
}
|
26
ResumeBuilder/Style/HeaderStyle.swift
Normal 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
|
||||
}
|
||||
}
|
27
ResumeBuilder/Style/SkillStyle.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|