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");