diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a06a8fc6c..5f928781e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,11 +11,11 @@ .github/workflows @bitwarden/dept-devops # DevOps for Version Bumping -src/Android/Properties/AndroidManifest.xml +src/App/Platforms/Android/AndroidManifest.xml src/iOS.Autofill/Info.plist src/iOS.Extension/Info.plist src/iOS.ShareExtension/Info.plist -src/iOS/Info.plist +src/App/Platforms/iOS/Info.plist ## Auth team files ## diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f40ce596c..e004c02ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -226,7 +226,7 @@ jobs: - name: Upload Prod .aab artifact if: ${{ matrix.variant == 'prod' }} - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab @@ -234,7 +234,7 @@ jobs: - name: Upload Prod .apk artifact if: ${{ matrix.variant == 'prod' }} - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk @@ -242,7 +242,7 @@ jobs: - name: Upload Other .apk artifact if: ${{ matrix.variant != 'prod' }} - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: com.x8bit.bitwarden.${{ matrix.variant }}.apk path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk @@ -262,7 +262,7 @@ jobs: - name: Upload .apk sha file for prod if: ${{ matrix.variant == 'prod' }} - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: bw-android-apk-sha256.txt path: ./bw-android-apk-sha256.txt @@ -270,7 +270,7 @@ jobs: - name: Upload .apk sha file for other if: ${{ matrix.variant != 'prod' }} - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: bw-android-${{ matrix.variant }}-apk-sha256.txt path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt @@ -422,7 +422,7 @@ jobs: Copy-Item $signedApkPath $signedApkDestPath - name: Upload F-Droid .apk artifact - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: com.x8bit.bitwarden-fdroid.apk path: ./com.x8bit.bitwarden-fdroid.apk @@ -434,7 +434,7 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt - name: Upload F-Droid sha file - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: bw-fdroid-apk-sha256.txt path: ./bw-fdroid-apk-sha256.txt @@ -529,6 +529,8 @@ jobs: echo "##### Setting CFBundleVersion $BUILD_NUMBER" echo "########################################" + echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY + perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist @@ -662,7 +664,7 @@ jobs: cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH - name: Upload App Store .ipa & dSYMs artifacts - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: Bitwarden iOS path: | @@ -671,7 +673,7 @@ jobs: if-no-files-found: error - name: Upload .app file for Automation CI - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: ${{ env.app_ci_output_filename }}.app.zip path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip @@ -768,7 +770,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.0 + uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 8424ca0e9..ff9bf3d24 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -30,7 +30,7 @@ jobs: secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" - name: Download translations - uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.0 + uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbcfe24cc..4cbddc808 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,7 +56,7 @@ jobs: - name: Create GitHub deployment if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -87,7 +87,7 @@ jobs: - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, @@ -147,7 +147,7 @@ jobs: name: com.x8bit.bitwarden-fdroid.apk - name: Set up Node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '16.x' diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 88b86acbd..1805c2f52 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -1,5 +1,5 @@ --- -name: Version Auto Bump +name: Auto Bump Mobile Version on: push: @@ -7,14 +7,12 @@ on: - v** jobs: - setup: - name: "Setup" + bump-version: + name: Bump Mobile Version runs-on: ubuntu-22.04 - outputs: - version_number: ${{ steps.version.outputs.new-version }} steps: - name: Checkout Branch - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Calculate bumped version id: version @@ -29,12 +27,23 @@ jobs: NEW_PATCH=$((CURR_PATCH+1)) NEW_VER=$CURR_MAJOR.$NEW_PATCH echo "New Version: $NEW_VER" - echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VER" >> $GITHUB_OUTPUT - trigger_version_bump: - name: Bump version to ${{ needs.setup.outputs.version_number }} - needs: setup - uses: ./.github/workflows/version-bump.yml - with: - version_number: ${{ needs.setup.outputs.version_number }} - secrets: inherit \ No newline at end of file + - name: Login to Azure - CI Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve bot secrets + id: retrieve-bot-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: bitwarden-ci + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: "Bump version to ${{ steps.version.outputs.new_version }}" + env: + GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + run: | + echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}"}' | \ + gh workflow run version-bump.yml --json --repo bitwarden/mobile \ No newline at end of file diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index bb24c76e2..20d525891 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -72,7 +72,7 @@ jobs: CURRENT_VERSION=$(xmllint --xpath ' string(/manifest/@*[local-name()="versionName" and namespace-uri()="http://schemas.android.com/apk/res/android"]) - ' src/Android/Properties/AndroidManifest.xml) + ' src/App/Platforms/Android/AndroidManifest.xml) # Error if version has not changed. if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then @@ -93,7 +93,7 @@ jobs: uses: bitwarden/gh-actions/version-bump@main with: version: ${{ inputs.version_number }} - file_path: "src/Android/Properties/AndroidManifest.xml" + file_path: "src/App/Platforms/Android/AndroidManifest.xml" - name: Bump Version - iOS.Autofill uses: bitwarden/gh-actions/version-bump@main @@ -117,7 +117,7 @@ jobs: uses: bitwarden/gh-actions/version-bump@main with: version: ${{ inputs.version_number }} - file_path: "src/iOS/Info.plist" + file_path: "src/App/Platforms/iOS/Info.plist" - name: Setup git run: | @@ -191,22 +191,26 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: main + + - name: Install xmllint + run: sudo apt install -y libxml2-utils - name: Verify version has been updated env: NEW_VERSION: ${{ inputs.version_number }} run: | - CURRENT_VERSION=$(xmllint --xpath ' + # Wait for version to change. + while : ; do + echo "Waiting for version to be updated..." + git pull --force + CURRENT_VERSION=$(xmllint --xpath ' string(/manifest/@*[local-name()="versionName" and namespace-uri()="http://schemas.android.com/apk/res/android"]) - ' src/Android/Properties/AndroidManifest.xml) + ' src/App/Platforms/Android/AndroidManifest.xml) - # Wait for version to change. - while [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] - do - echo "Waiting for version to be updated..." + # If the versions don't match we continue the loop, otherwise we break out of the loop. + [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break sleep 10 - git pull --force done - name: Cut RC branch diff --git a/crowdin.yml b/crowdin.yml index 45e3e516d..9ed426f74 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -2,9 +2,9 @@ project_id_env: _CROWDIN_PROJECT_ID api_token_env: CROWDIN_API_TOKEN preserve_hierarchy: true files: - - source: /src/App/Resources/AppResources.resx - dest: /src/App/Resources/%original_file_name% - translation: /src/App/Resources/AppResources.%two_letters_code%.resx + - source: /src/Core/Resources/Localization/AppResources.resx + dest: /src/Core/Resources/Localization/%original_file_name% + translation: /src/Core/Resources/Localization/AppResources.%two_letters_code%.resx update_option: update_as_unapproved languages_mapping: two_letters_code: diff --git a/src/App/Platforms/Android/AndroidManifest.xml b/src/App/Platforms/Android/AndroidManifest.xml index 9f703c01b..526782deb 100644 --- a/src/App/Platforms/Android/AndroidManifest.xml +++ b/src/App/Platforms/Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/src/App/Platforms/Android/Services/AndroidPushNotificationService.cs b/src/App/Platforms/Android/Services/AndroidPushNotificationService.cs index 2492318bc..30187a161 100644 --- a/src/App/Platforms/Android/Services/AndroidPushNotificationService.cs +++ b/src/App/Platforms/Android/Services/AndroidPushNotificationService.cs @@ -79,24 +79,29 @@ namespace Bit.Droid.Services } var context = Android.App.Application.Context; - var intent = new Intent(context, typeof(MainActivity)); - intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); - var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true); - var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags); + var intent = context.PackageManager?.GetLaunchIntentForPackage(context.PackageName ?? string.Empty); - var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver)); - deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); - var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags); + var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId); + if(intent != null && context.PackageManager != null && !string.IsNullOrEmpty(context.PackageName)) + { + intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); + var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true); + var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags); - var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId) - .SetContentIntent(pendingIntent) - .SetContentTitle(title) + var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver)); + deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data)); + var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags); + + builder.SetContentIntent(pendingIntent) + .SetDeleteIntent(deletePendingIntent); + } + + builder.SetContentTitle(title) .SetContentText(message) .SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification) .SetColor((int)Android.Graphics.Color.White) - .SetDeleteIntent(deletePendingIntent) .SetAutoCancel(true); - + if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0) { builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000); diff --git a/src/App/Platforms/iOS/Info.plist b/src/App/Platforms/iOS/Info.plist index d82c64a97..a89c66880 100644 --- a/src/App/Platforms/iOS/Info.plist +++ b/src/App/Platforms/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2024.2.1 + 2024.2.2 CFBundleVersion 1 CFBundleIconName diff --git a/src/Core/Utilities/ThemeManager.cs b/src/Core/Utilities/ThemeManager.cs index ce12dcd66..6ff895eb9 100644 --- a/src/Core/Utilities/ThemeManager.cs +++ b/src/Core/Utilities/ThemeManager.cs @@ -9,6 +9,10 @@ using Bit.Core.Utilities; using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Controls; using Microsoft.Maui; +#if IOS +using Foundation; +using UIKit; +#endif namespace Bit.App.Utilities { @@ -65,12 +69,11 @@ namespace Bit.App.Utilities resources.MergedDictionaries.Add(new ControlTemplates()); // Platform styles - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.Android) + if (DeviceInfo.Platform == DevicePlatform.Android) { resources.MergedDictionaries.Add(new Styles.Android()); } - else if (Device.RuntimePlatform == Device.iOS) + else if (DeviceInfo.Platform == DevicePlatform.iOS) { resources.MergedDictionaries.Add(new iOS()); } @@ -147,17 +150,47 @@ namespace Bit.App.Utilities return stateService.GetAutoDarkThemeAsync().GetAwaiter().GetResult(); } + //HACK: OsDarkModeEnabled() is divided into Android and iOS implementations due to a MAUI bug. + // Currently on iOS when resuming the app after showing a System "Share/Sheet" (or other similar UI) + // MAUI reports the incorrect Theme. To avoid this we are fetching the current OS Theme directly on iOS from the iOS API. + // MAUI Issue: https://github.com/dotnet/maui/issues/19614 public static bool OsDarkModeEnabled() { - if (Application.Current == null) - { - // called from iOS extension - var app = new App(new AppOptions { IosExtension = true }); - return app.RequestedTheme == AppTheme.Dark; - } +#if UT + return false; +#else + +#if ANDROID return Application.Current.RequestedTheme == AppTheme.Dark; +#else + var requestedTheme = AppTheme.Unspecified; + if (!OperatingSystem.IsIOSVersionAtLeast(13, 0)) + return false; + + var traits = InvokeOnMainThread(() => WindowStateManager.Default.GetCurrentUIViewController()?.TraitCollection) ?? UITraitCollection.CurrentTraitCollection; + var uiStyle = traits.UserInterfaceStyle; + + requestedTheme = uiStyle switch + { + UIUserInterfaceStyle.Light => AppTheme.Light, + UIUserInterfaceStyle.Dark => AppTheme.Dark, + _ => AppTheme.Unspecified + }; + return requestedTheme == AppTheme.Dark; +#endif + +#endif } +#if IOS + private static T InvokeOnMainThread(Func factory) + { + T value = default; + NSRunLoop.Main.InvokeOnMainThread(() => value = factory()); + return value; + } +#endif + public static void ApplyResourcesTo(VisualElement element) { foreach (var resourceDict in Resources().MergedDictionaries) diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index fc7d461fc..361cbacbe 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; using AuthenticationServices; using Bit.App.Abstractions; diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index 71b88905c..d7d964b94 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2024.2.1 + 2024.2.2 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index 24289c18e..c4d38502e 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2024.2.1 + 2024.2.2 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index ba68616b6..7bbd05352 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2024.2.1 + 2024.2.2 CFBundleVersion 1 MinimumOSVersion