diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 90c8504a5..f40ce596c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -80,7 +80,7 @@ jobs:
dotnet-version: '8.0.x'
- name: Set up MSBuild
- uses: microsoft/setup-msbuild@031090342aeefe171e49f3820f3b52110c66e402 # v1.3.2
+ uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
@@ -310,7 +310,7 @@ jobs:
dotnet-version: '8.0.x'
- name: Set up MSBuild
- uses: microsoft/setup-msbuild@031090342aeefe171e49f3820f3b52110c66e402 # v1.3.2
+ uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
@@ -366,13 +366,6 @@ jobs:
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
- # Write-Output "########################################"
- # Write-Output "##### Clean Android and App"
- # Write-Output "########################################"
-
- # msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
- # msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
-
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
@@ -392,42 +385,6 @@ jobs:
$xml.Save($androidManifest);
- # Write-Output "########################################"
- # Write-Output "##### Uninstall from App.csproj"
- # Write-Output "########################################"
-
- # $xml=New-Object XML;
- # $xml.Load($appPath);
-
- # $ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
- # $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
-
- # $firebaseNode=$xml.SelectSingleNode(`
- # "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
- # $firebaseNode.ParentNode.RemoveChild($firebaseNode);
-
- # $daggerNode=$xml.SelectSingleNode(`
- # "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
- # $daggerNode.ParentNode.RemoveChild($daggerNode);
-
- # $safetyNetNode=$xml.SelectSingleNode(`
- # "/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
- # $safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
-
- # $xml.Save($appPath);
-
- # Write-Output "########################################"
- # Write-Output "##### Uninstall from Core.csproj"
- # Write-Output "########################################"
-
- # $xml=New-Object XML;
- # $xml.Load($corePath);
-
- # $appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
- # $appCenterNode.ParentNode.RemoveChild($appCenterNode);
-
- # $xml.Save($corePath);
-
- name: Restore packages
run: dotnet restore
@@ -811,7 +768,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
- uses: crowdin/github-action@6fb7e99759b996fd142995636fd8c88c1fb8ecd9 # v1.16.1
+ uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.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 5da9478ae..8424ca0e9 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@6fb7e99759b996fd142995636fd8c88c1fb8ecd9 # v1.16.1
+ uses: crowdin/github-action@97bef4fd3f1b853eb105bc99b8d0d563760e024c # v1.17.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 95dc4f463..fbcfe24cc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -68,7 +68,7 @@ jobs:
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
- uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2.28.0
+ uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -76,7 +76,7 @@ jobs:
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
- uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2.28.0
+ uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -130,7 +130,7 @@ jobs:
- name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
- uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2.28.0
+ uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -139,7 +139,7 @@ jobs:
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
- uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2.28.0
+ uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml
index 92d7fe374..bb24c76e2 100644
--- a/.github/workflows/version-bump.yml
+++ b/.github/workflows/version-bump.yml
@@ -36,10 +36,19 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- repository: bitwarden/mobile
+
+ - name: Check if RC branch exists
+ if: ${{ inputs.cut_rc_branch == true }}
+ run: |
+ remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
+ if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
+ echo "Remote RC branch exists."
+ echo "Please delete current RC branch before running again."
+ exit 1
+ fi
- name: Import GPG key
- uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
+ uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
@@ -183,14 +192,22 @@ jobs:
with:
ref: main
- - name: Check if RC branch exists
+ - name: Verify version has been updated
+ env:
+ NEW_VERSION: ${{ inputs.version_number }}
run: |
- remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
- if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
- echo "Remote RC branch exists."
- echo "Please delete current RC branch before running again."
- exit 1
- fi
+ CURRENT_VERSION=$(xmllint --xpath '
+ string(/manifest/@*[local-name()="versionName"
+ and namespace-uri()="http://schemas.android.com/apk/res/android"])
+ ' src/Android/Properties/AndroidManifest.xml)
+
+ # Wait for version to change.
+ while [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]]
+ do
+ echo "Waiting for version to be updated..."
+ sleep 10
+ git pull --force
+ done
- name: Cut RC branch
run: |
diff --git a/src/App/Platforms/Android/MainActivity.cs b/src/App/Platforms/Android/MainActivity.cs
index 88fbceb8c..fe852fc7e 100644
--- a/src/App/Platforms/Android/MainActivity.cs
+++ b/src/App/Platforms/Android/MainActivity.cs
@@ -76,7 +76,8 @@ namespace Bit.Droid
//We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs
_appOptions = GetOptions();
- ((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetOptions(_appOptions);
+ //This does not replace existing Options in App.xaml.cs if it exists already. It only updates properties in Options related with Autofill/CreateSend/etc..
+ ((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetAndroidOptions(_appOptions);
base.OnCreate(savedInstanceState);
diff --git a/src/App/Platforms/iOS/AppDelegate.cs b/src/App/Platforms/iOS/AppDelegate.cs
index c0a31b145..066eb54bc 100644
--- a/src/App/Platforms/iOS/AppDelegate.cs
+++ b/src/App/Platforms/iOS/AppDelegate.cs
@@ -460,21 +460,20 @@ namespace Bit.iOS
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
- // TODO: Uncomment, this is just a test to see if this is causing the background crash on release when sending the app to background
- //MainThread.BeginInvokeOnMainThread(() =>
- //{
- // try
- // {
- // _eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
- // {
- // _eventService?.UploadEventsAsync().FireAndForget();
- // });
- // }
- // catch (Exception ex)
- // {
- // LoggerHelper.LogEvenIfCantBeResolved(ex);
- // }
- //});
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ try
+ {
+ _eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
+ {
+ _eventService?.UploadEventsAsync().FireAndForget();
+ });
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.LogEvenIfCantBeResolved(ex);
+ }
+ });
}
private async Task StopEventTimerAsync()
@@ -484,20 +483,19 @@ namespace Bit.iOS
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
- // TODO: Uncomment, this is just a test to see if this is causing the background crash on release when sending the app to background
- //if (_eventBackgroundTaskId > 0)
- //{
- // UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
- // _eventBackgroundTaskId = 0;
- //}
- //_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
- //{
- // UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
- // _eventBackgroundTaskId = 0;
- //});
- //await _eventService.UploadEventsAsync();
- //UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
- //_eventBackgroundTaskId = 0;
+ if (_eventBackgroundTaskId > 0)
+ {
+ UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
+ _eventBackgroundTaskId = 0;
+ }
+ _eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
+ {
+ UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
+ _eventBackgroundTaskId = 0;
+ });
+ await _eventService.UploadEventsAsync();
+ UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
+ _eventBackgroundTaskId = 0;
}
catch (Exception ex)
{
diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs
index 4f2fa1b3a..25b882d72 100644
--- a/src/Core/App.xaml.cs
+++ b/src/Core/App.xaml.cs
@@ -85,10 +85,27 @@ namespace Bit.App
}
}
- //Allows setting Options from MainActivity before base.OnCreate
- public void SetOptions(AppOptions appOptions)
+ ///
+ /// Allows setting Options from MainActivity before base.OnCreate
+ /// Note 1: This is only be used by Android due to way it's Lifecycle works
+ /// Note 2: This method does not replace existing Options in App.xaml.cs if it exists already.
+ /// It only updates properties in Options related with Autofill/CreateSend/etc..
+ ///
+ /// Options created in Android MainActivity.cs
+ public void SetAndroidOptions(AppOptions appOptions)
{
- Options = appOptions ?? new AppOptions();
+ if (Options == null)
+ {
+ Options = appOptions ?? new AppOptions();
+ }
+ else if(appOptions != null)
+ {
+ Options.Uri = appOptions.Uri;
+ Options.MyVaultTile = appOptions.MyVaultTile;
+ Options.GeneratorTile = appOptions.GeneratorTile;
+ Options.FromAutofillFramework = appOptions.FromAutofillFramework;
+ Options.CreateSend = appOptions.CreateSend;
+ }
}
protected override Window CreateWindow(IActivationState activationState)
diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
index d84f941d1..99c56791c 100644
--- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
+++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
@@ -1,5 +1,5 @@
-
-
+
-
-
-
+
+
+
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
index 297dc0e4b..2e3b4240a 100644
--- a/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
+++ b/src/Core/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
@@ -1,10 +1,14 @@
namespace Bit.App.Controls
{
- public partial class AuthenticatorViewCell : ExtendedGrid
+ public partial class AuthenticatorViewCell : BaseCipherViewCell
{
public AuthenticatorViewCell()
{
InitializeComponent();
}
+
+ protected override CachedImage Icon => _iconImage;
+
+ protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
}
}
diff --git a/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs b/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs
new file mode 100644
index 000000000..15bd0dd7f
--- /dev/null
+++ b/src/Core/Controls/CipherViewCell/BaseCipherViewCell.cs
@@ -0,0 +1,103 @@
+using Bit.App.Pages;
+
+namespace Bit.App.Controls
+{
+ public abstract class BaseCipherViewCell : ExtendedGrid
+ {
+ protected virtual CachedImage Icon { get; }
+
+ protected virtual IconLabel IconPlaceholder { get; }
+
+ // HACK: PM-5896 Fix for Background Crash on iOS
+ // While loading the cipher icon and the user sent the app to background
+ // the app was crashing sometimes when the "LoadingPlaceholder" or "ErrorPlaceholder"
+ // were being accessed, thus locked, and as soon the app got suspended by the OS
+ // the app would crash because there can't be any lock files by the app when it gets suspended.
+ // So, the approach has changed to reuse the IconLabel default icon to use it for these placeholders
+ // as well. In order to do that both icon controls change their visibility dynamically here reacting to
+ // CachedImage events and binding context changes.
+
+ protected override void OnBindingContextChanged()
+ {
+ Icon.Source = null;
+ if (BindingContext is CipherItemViewModel cipherItemVM)
+ {
+ Icon.Source = cipherItemVM.IconImageSource;
+ if (!cipherItemVM.IconImageSuccesfullyLoaded)
+ {
+ UpdateIconImages(cipherItemVM.ShowIconImage);
+ }
+ }
+
+ base.OnBindingContextChanged();
+ }
+
+ private void UpdateIconImages(bool showIcon)
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ if (!showIcon)
+ {
+ Icon.IsVisible = false;
+ IconPlaceholder.IsVisible = true;
+ return;
+ }
+
+ IconPlaceholder.IsVisible = Icon.IsLoading;
+ });
+ }
+
+ public void Icon_Success(object sender, FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs e)
+ {
+ if (BindingContext is CipherItemViewModel cipherItemVM)
+ {
+ cipherItemVM.IconImageSuccesfullyLoaded = true;
+ }
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ Icon.IsVisible = true;
+ IconPlaceholder.IsVisible = false;
+ });
+ }
+
+ public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)
+ {
+ if (BindingContext is CipherItemViewModel cipherItemVM)
+ {
+ cipherItemVM.IconImageSuccesfullyLoaded = false;
+ }
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ Icon.IsVisible = false;
+ IconPlaceholder.IsVisible = true;
+ });
+ }
+ }
+
+ public class StubBaseCipherViewCellSoLinkerDoesntRemoveMethods : BaseCipherViewCell
+ {
+ protected override CachedImage Icon => new CachedImage();
+ protected override IconLabel IconPlaceholder => new IconLabel();
+
+ public static void CallThisSoLinkerDoesntRemoveMethods()
+ {
+ var stub = new StubBaseCipherViewCellSoLinkerDoesntRemoveMethods();
+
+ try
+ {
+ stub.Icon_Success(stub, new FFImageLoading.Maui.CachedImageEvents.SuccessEventArgs(new FFImageLoading.Work.ImageInformation(), FFImageLoading.Work.LoadingResult.Disk));
+ }
+ catch (Exception)
+ {
+ }
+
+ try
+ {
+ stub.Icon_Error(stub, new FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs(new InvalidOperationException("stub")));
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+}
diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml
index 6a492f47e..bbfdd41ee 100644
--- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml
+++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml
@@ -1,5 +1,5 @@
-
-
-
-
+
+
@@ -121,4 +119,4 @@
SemanticProperties.Description="{u:I18n Options}"
AutomationId="CipherOptionsButton" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs
index bea341a46..fd163bce3 100644
--- a/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs
+++ b/src/Core/Controls/CipherViewCell/CipherViewCell.xaml.cs
@@ -5,7 +5,7 @@ using Bit.Core.Utilities;
namespace Bit.App.Controls
{
- public partial class CipherViewCell : ExtendedGrid
+ public partial class CipherViewCell : BaseCipherViewCell
{
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
@@ -23,6 +23,10 @@ namespace Bit.App.Controls
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
}
+ protected override CachedImage Icon => _iconImage;
+
+ protected override IconLabel IconPlaceholder => _iconPlaceholderImage;
+
public ICommand ButtonCommand
{
get => GetValue(ButtonCommandProperty) as ICommand;
diff --git a/src/Core/MauiProgram.cs b/src/Core/MauiProgram.cs
index 275e31470..43fdfd843 100644
--- a/src/Core/MauiProgram.cs
+++ b/src/Core/MauiProgram.cs
@@ -1,4 +1,5 @@
-using Camera.MAUI;
+using Bit.App.Controls;
+using Camera.MAUI;
using CommunityToolkit.Maui;
#if !UT
using FFImageLoading.Maui;
@@ -62,6 +63,13 @@ public static class MauiProgram
builder.Logging.AddDebug();
#endif
+ ExplicitlyPreventThingsGetRemovedBecauseOfLinker();
+
return builder;
}
+
+ private static void ExplicitlyPreventThingsGetRemovedBecauseOfLinker()
+ {
+ StubBaseCipherViewCellSoLinkerDoesntRemoveMethods.CallThisSoLinkerDoesntRemoveMethods();
+ }
}
diff --git a/src/Core/Pages/Vault/CipherItemViewModel.cs b/src/Core/Pages/Vault/CipherItemViewModel.cs
index 1ee850a23..28dee6780 100644
--- a/src/Core/Pages/Vault/CipherItemViewModel.cs
+++ b/src/Core/Pages/Vault/CipherItemViewModel.cs
@@ -38,5 +38,11 @@ namespace Bit.App.Pages
return _iconImageSource;
}
}
+
+ ///
+ /// Flag that indicates if FFImageLoading has successfully finished loading the image.
+ /// This is useful to check when the cell is being reused.
+ ///
+ public bool IconImageSuccesfullyLoaded { get; set; }
}
}