1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-07 19:13:19 +00:00

Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys

This commit is contained in:
Federico Maccaroni
2024-02-05 14:31:44 -03:00
14 changed files with 244 additions and 132 deletions

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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: |

View File

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

View File

@@ -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)
{

View File

@@ -85,10 +85,27 @@ namespace Bit.App
}
}
//Allows setting Options from MainActivity before base.OnCreate
public void SetOptions(AppOptions appOptions)
/// <summary>
/// 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..
/// </summary>
/// <param name="appOptions">Options created in Android MainActivity.cs</param>
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)

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
@@ -13,34 +13,33 @@
RowSpacing="0"
Padding="0,10,0,0"
RowDefinitions="*,*">
<Grid.Resources>
<controls:BaseCipherViewCell.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
<u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources>
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
</controls:BaseCipherViewCell.Resources>
<controls:CachedImage
x:Name="_iconImage"
Grid.Column="0"
Grid.RowSpan="2"
BitmapOptimizations="True"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
Success="Icon_Success"
Error="Icon_Error"
AutomationProperties.IsInAccessibleTree="False" />
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
Grid.RowSpan="2"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<!-- ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png" -->
<Label
LineBreakMode="TailTruncation"
@@ -124,4 +123,4 @@
HorizontalOptions="Center"
VerticalOptions="Center"
SemanticProperties.Description="{u:I18n CopyTotp}" />
</controls:ExtendedGrid>
</controls:BaseCipherViewCell>

View File

@@ -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;
}
}

View File

@@ -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)
{
}
}
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
<controls:BaseCipherViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
@@ -29,17 +29,6 @@
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<controls:CachedImage
x:Name="_iconImage"
Grid.Column="0"
@@ -50,12 +39,21 @@
WidthRequest="22"
HeightRequest="22"
Aspect="AspectFit"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
Success="Icon_Success"
Error="Icon_Error"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherWebsiteIcon" />
<!-- ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png" -->
<controls:IconLabel
x:Name="_iconPlaceholderImage"
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
@@ -121,4 +119,4 @@
SemanticProperties.Description="{u:I18n Options}"
AutomationId="CipherOptionsButton" />
</controls:ExtendedGrid>
</controls:BaseCipherViewCell>

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -38,5 +38,11 @@ namespace Bit.App.Pages
return _iconImageSource;
}
}
/// <summary>
/// Flag that indicates if FFImageLoading has successfully finished loading the image.
/// This is useful to check when the cell is being reused.
/// </summary>
public bool IconImageSuccesfullyLoaded { get; set; }
}
}