diff --git a/appIcons/Android/beta-layered-excluded.svg b/appIcons/Android/beta-layered-excluded.svg new file mode 100644 index 000000000..dc39f61aa --- /dev/null +++ b/appIcons/Android/beta-layered-excluded.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/appIcons/Android/beta-layered.svg b/appIcons/Android/beta-layered.svg new file mode 100644 index 000000000..73d5e8f3b --- /dev/null +++ b/appIcons/Android/beta-layered.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/appIcons/Android/dev-layered-excluded.svg b/appIcons/Android/dev-layered-excluded.svg new file mode 100644 index 000000000..5f8b22a4b --- /dev/null +++ b/appIcons/Android/dev-layered-excluded.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/appIcons/Android/dev-layered.svg b/appIcons/Android/dev-layered.svg new file mode 100644 index 000000000..cfe9c9a7c --- /dev/null +++ b/appIcons/Android/dev-layered.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/appIcons/Android/qa-layered-excluded.svg b/appIcons/Android/qa-layered-excluded.svg new file mode 100644 index 000000000..c2641004c --- /dev/null +++ b/appIcons/Android/qa-layered-excluded.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/appIcons/Android/qa-layered.svg b/appIcons/Android/qa-layered.svg new file mode 100644 index 000000000..ca2422b87 --- /dev/null +++ b/appIcons/Android/qa-layered.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/appIcons/iOS/beta.png b/appIcons/iOS/beta.png new file mode 100644 index 000000000..04623a250 Binary files /dev/null and b/appIcons/iOS/beta.png differ diff --git a/appIcons/iOS/dev.png b/appIcons/iOS/dev.png new file mode 100644 index 000000000..0106cdf96 Binary files /dev/null and b/appIcons/iOS/dev.png differ diff --git a/appIcons/iOS/prod.png b/appIcons/iOS/prod.png new file mode 100644 index 000000000..a3d47292d Binary files /dev/null and b/appIcons/iOS/prod.png differ diff --git a/appIcons/iOS/qa.png b/appIcons/iOS/qa.png new file mode 100644 index 000000000..adff0ba35 Binary files /dev/null and b/appIcons/iOS/qa.png differ diff --git a/appIcons/icongen.sh b/appIcons/icongen.sh new file mode 100755 index 000000000..d3469a3c6 --- /dev/null +++ b/appIcons/icongen.sh @@ -0,0 +1,136 @@ +#! /bin/sh + +function print_example() { + echo "Example" + echo " icons ios ~/AppIcon.pdf ~/Icons/" +} + +function print_usage() { + echo "Usage" + echo " icons in-file.pdf (out-dir)" +} + +function command_exists() { + if type "$1" >/dev/null 2>&1; then + return 1 + else + return 0 + fi +} + +if command_exists "sips" == 0 ; then + echo "sips tool not found" + exit 1 +fi + +if [ "$1" = "--help" ] || [ "$1" = "-h" ] ; then + print_usage + exit 0 +fi + +PLATFORM="$1" +FILE="$2" +if [ -z "$PLATFORM" ] || [ -z "$FILE" ] ; then + echo "Error: missing arguments" + echo "" + print_usage + echo "" + print_example + exit 1 +fi + +DIR="$3" +if [ -z "$DIR" ] ; then + DIR=$(dirname $FILE) +fi + +# Create directory if needed +mkdir -p "$DIR" + +if [[ "$PLATFORM" == *"ios"* ]] ; then # iOS + sips -s format png -Z '180' "${FILE}" --out "${DIR}"/Icon-180.png + sips -s format png -Z '29' "${FILE}" --out "${DIR}"/Icon-29.png + sips -s format png -Z '58' "${FILE}" --out "${DIR}"/Icon-58.png + sips -s format png -Z '120' "${FILE}" --out "${DIR}"/Icon-120.png + sips -s format png -Z '87' "${FILE}" --out "${DIR}"/Icon-87.png + sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Icon-40.png + sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Icon-80.png + sips -s format png -Z '76' "${FILE}" --out "${DIR}"/Icon-76.png + sips -s format png -Z '152' "${FILE}" --out "${DIR}"/Icon-152.png + sips -s format png -Z '167' "${FILE}" --out "${DIR}"/Icon-167.png + sips -s format png -Z '60' "${FILE}" --out "${DIR}"/Icon-60.png + sips -s format png -Z '20' "${FILE}" --out "${DIR}"/Icon-20.png + sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/Icon-1024.png + + # https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html + contents_json='{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPad.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPad@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadPro@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppStoreMarketing.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}' + echo $contents_json > "${DIR}"/Contents.json +fi + +if [[ "$PLATFORM" == *"watch"* ]] ; then # Apple Watch + sips -s format png -Z '48' "${FILE}" --out "${DIR}"/Watch38mmNotificationCenter.png + sips -s format png -Z '55' "${FILE}" --out "${DIR}"/Watch42mmNotificationCenter.png + sips -s format png -Z '66' "${FILE}" --out "${DIR}"/Watch66NotificationCenter.png + sips -s format png -Z '58' "${FILE}" --out "${DIR}"/WatchCompanionSettings@2x.png + sips -s format png -Z '87' "${FILE}" --out "${DIR}"/WatchCompanionSettings@3x.png + sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Watch38MM42MMHomeScreen.png + sips -s format png -Z '88' "${FILE}" --out "${DIR}"/Watch40MMHomeScreen.png + sips -s format png -Z '92' "${FILE}" --out "${DIR}"/Watch41MMHomeScreen.png + sips -s format png -Z '100' "${FILE}" --out "${DIR}"/Watch44MMHomeScreen.png + sips -s format png -Z '102' "${FILE}" --out "${DIR}"/Watch45MMHomeScreen.png + sips -s format png -Z '108' "${FILE}" --out "${DIR}"/Watch49MMHomeScreen.png + sips -s format png -Z '172' "${FILE}" --out "${DIR}"/Watch38MMShortLook.png + sips -s format png -Z '196' "${FILE}" --out "${DIR}"/Watch40MM42MMShortLook.png + sips -s format png -Z '216' "${FILE}" --out "${DIR}"/Watch44MMShortLook.png + sips -s format png -Z '234' "${FILE}" --out "${DIR}"/Watch234ShortLook.png + sips -s format png -Z '258' "${FILE}" --out "${DIR}"/Watch258ShortLook.png + sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/WatchAppStore.png + + # https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html + contents_json='{"images":[{"size":"24x24","idiom":"watch","scale":"2x","filename":"Watch38mmNotificationCenter.png","role":"notificationCenter","subtype":"38mm"},{"size":"27.5x27.5","idiom":"watch","scale":"2x","filename":"Watch42mmNotificationCenter.png","role":"notificationCenter","subtype":"42mm"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@2x.png","role":"companionSettings","scale":"2x"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@3x.png","role":"companionSettings","scale":"3x"},{"size":"40x40","idiom":"watch","filename":"Watch38MM42MMHomeScreen.png","scale":"2x","role":"appLauncher","subtype":"38mm"},{"size":"44x44","idiom":"watch","scale":"2x","filename":"Watch40MMHomeScreen.png","role":"appLauncher","subtype":"40mm"},{"size":"50x50","idiom":"watch","scale":"2x","filename":"Watch44MMHomeScreen.png","role":"appLauncher","subtype":"44mm"},{"size":"86x86","idiom":"watch","scale":"2x","filename":"Watch38MMShortLook.png","role":"quickLook","subtype":"38mm"},{"size":"98x98","idiom":"watch","scale":"2x","filename":"Watch40MM42MMShortLook.png","role":"quickLook","subtype":"42mm"},{"size":"108x108","idiom":"watch","scale":"2x","filename":"Watch44MMShortLook.png","role":"quickLook","subtype":"44mm"},{"idiom":"watch-marketing","filename":"WatchAppStore.png","size":"1024x1024","scale":"1x"}],"info":{"version":1,"author":"xcode"}}' + echo $contents_json > "${DIR}"/Contents.json +fi + +if [[ "$PLATFORM" == *"complication"* ]] ; then # Apple Watch + sips -s format png -Z '32' "${FILE}" --out "${DIR}"/Circular38mm2x.png + sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular40mm2x.png + sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular42mm2x.png + sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Circular44mm2x.png + sips -s format png -Z '182' "${FILE}" --out "${DIR}"/ExtraLarge38mm2x.png + sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge40mm2x.png + sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge42mm2x.png + sips -s format png -Z '224' "${FILE}" --out "${DIR}"/ExtraLarge44mm2x.png + sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel40mm2x.png + sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel42mm2x.png + sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicBezel44mm2x.png + sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular40mm2x.png + sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular42mm2x.png + sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicCircular44mm2x.png + sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner40mm2x.png + sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner42mm2x.png + sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicCorner44mm2x.png + sips -s format png -Z '52' "${FILE}" --out "${DIR}"/GraphicModular38mm2x.png + sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular40mm2x.png + sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular42mm2x.png + sips -s format png -Z '64' "${FILE}" --out "${DIR}"/GraphicModular44mm2x.png + sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicUtilitarian38mm2x.png + sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian40mm2x.png + sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian42mm2x.png + sips -s format png -Z '50' "${FILE}" --out "${DIR}"/GraphicUtilitarian44mm2x.png + sips -s format png -Z '206' "${FILE}" --out "${DIR}"/GraphicExtraLarge38mm2x.png + sips -s format png -Z '264' "${FILE}" --out "${DIR}"/GraphicExtraLarge44mm2x.png + echo "NOTE: Graphic Extra Large is not generated since that is not rectangular" +fi + +if [[ "$PLATFORM" == *"macos"* ]] ; then # macOS + sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/icon_512x512@2x.png + sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_512x512.png + sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_256x256@2x.png + sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_256x256.png + sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_128x128@2x.png + sips -s format png -Z '128' "${FILE}" --out "${DIR}"/icon_128x128.png + sips -s format png -Z '64' "${FILE}" --out "${DIR}"/icon_32x32@2x.png + sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_32x32.png + sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_16x16@2x.png + sips -s format png -Z '16' "${FILE}" --out "${DIR}"/icon_16x16.png +fi \ No newline at end of file diff --git a/build.cake b/build.cake index f30c8404b..466a96cc8 100644 --- a/build.cake +++ b/build.cake @@ -4,6 +4,7 @@ #addin nuget:?package=Cake.Incubator&version=7.0.0 #tool dotnet:?package=GitVersion.Tool&version=5.10.3 using Path = System.IO.Path; +using System.Text.RegularExpressions; var debugScript = Argument("debugScript", false); var target = Argument("target", "Default"); @@ -35,6 +36,7 @@ VariantConfig GetVariant() => variant.ToLower() switch{ GitVersion _gitVersion; //will be set by GetGitInfo task var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task +string _iOSVersionName = string.Empty; //will be set by UpdateiOSPlist task string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}"; string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git); int CreateBuildNumber(int previousNumber) => ++previousNumber; @@ -163,7 +165,8 @@ enum iOSProjectType MainApp, Autofill, Extension, - ShareExtension + ShareExtension, + WatchApp } string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch @@ -171,6 +174,7 @@ string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill", iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension", iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension", + iOSProjectType.WatchApp => $"{buildVariant.iOSBundleId}.watchkitapp", _ => buildVariant.iOSBundleId }; @@ -205,6 +209,7 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi if(projectType == iOSProjectType.MainApp) { + _iOSVersionName = newVersionName; plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url"; } @@ -240,10 +245,79 @@ private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig b Information($"{entitlementsPath} updated with success!"); } -Task("UpdateiOSIcon") +private void UpdateWatchKitAppInfoPlist(string plistPath, VariantConfig buildVariant) +{ + var plistFile = File(plistPath); + dynamic plist = DeserializePlist(plistFile); + + var prevBundleId = plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"]; + var newBundleId = GetiOSBundleId(buildVariant, iOSProjectType.WatchApp); + + plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = newBundleId; + + SerializePlist(plistFile, plist); + + Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}"); + Information($"{plistPath} updated with success!"); +} + +private void UpdateWatchPbxproj(string pbxprojPath, string newVersion) +{ + var fileText = FileReadText(pbxprojPath); + if (string.IsNullOrEmpty(fileText)) + { + throw new Exception($"Couldn't find {pbxprojPath}"); + } + + const string pattern = @"MARKETING_VERSION = [^;]*;"; + + fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};"); + + FileWriteText(pbxprojPath, fileText); + Information($"{pbxprojPath} modified successfully."); +} + +/// +/// Updates the target icons on the given appiconset target +/// taking as source the icon in appIcons/iOS folder for the giving variant +/// +/// It can be +/// Folder to copy the generated icons to +private void UpdateAppleIcons(string target, string appiconsetTarget) +{ + Information($"Updating {target} App Icons"); + + var iconsTempDirPath = Path.Combine(_slnPath, "appIcons", "temp"); + CreateDirectory(iconsTempDirPath); + + var arguments = new ProcessArgumentBuilder(); + arguments.Append(target); + arguments.Append(Path.Combine(_slnPath, "appIcons", "iOS", $"{variant}.png")); + arguments.Append(iconsTempDirPath); + + using(var process = StartAndReturnProcess(Path.Combine(_slnPath, "appIcons", "icongen.sh"), + new ProcessSettings { Arguments = arguments })) + { + process.WaitForExit(); + Information("Exit code: {0}", process.GetExitCode()); + } + + var generatedIconsPath = Path.Combine(iconsTempDirPath, "*.png"); + CopyFiles(generatedIconsPath, appiconsetTarget); + + DeleteDirectory(iconsTempDirPath, new DeleteDirectorySettings { + Recursive = true, + Force = true + }); + + Information($"{target} App Icons have been updated"); +} + +Task("UpdateiOSIcons") .Does(()=>{ - //TODO we'll implement variant icons later - Information($"Updating IOS App Icon"); + UpdateAppleIcons("ios", Path.Combine(_slnPath, "src", "iOS", "Resources", "Assets.xcassets", "AppIcons.appiconset")); + UpdateAppleIcons("watch", Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit App", "Assets.xcassets", "AppIcon.appiconset")); + // TODO: Update complication icons when they start working }); Task("UpdateiOSPlist") @@ -296,8 +370,10 @@ Task("UpdateiOSCodeFiles") var fileList = new string[] { Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"), Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"), + Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj"), + Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Helpers", "KeychainHelper.swift"), Path.Combine(".github", "resources", "export-options-ad-hoc.plist"), - Path.Combine(".github", "resources", "export-options-app-store.plist"), + Path.Combine(".github", "resources", "export-options-app-store.plist") }; foreach(string path in fileList) @@ -305,6 +381,22 @@ Task("UpdateiOSCodeFiles") ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId); } }); + +Task("UpdateWatchProject") + .IsDependentOn("UpdateiOSPlist") + .WithCriteria(() => !string.IsNullOrEmpty(_iOSVersionName)) + .Does(()=> { + var watchProjectPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj"); + UpdateWatchPbxproj(watchProjectPath, _iOSVersionName); + }); + +Task("UpdateWatchKitAppInfoPlist") + .Does(()=> { + var buildVariant = GetVariant(); + var infoPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Info.plist"); + UpdateWatchKitAppInfoPlist(infoPath, buildVariant); + }); + #endregion iOS #region Main Tasks @@ -318,12 +410,14 @@ Task("Android") }); Task("iOS") - //.IsDependentOn("UpdateiOSIcon") + .IsDependentOn("UpdateiOSIcons") .IsDependentOn("UpdateiOSPlist") .IsDependentOn("UpdateiOSAutofillPlist") .IsDependentOn("UpdateiOSExtensionPlist") .IsDependentOn("UpdateiOSShareExtensionPlist") .IsDependentOn("UpdateiOSCodeFiles") + .IsDependentOn("UpdateWatchProject") + .IsDependentOn("UpdateWatchKitAppInfoPlist") .Does(()=> { Information("iOS app updated");