1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-08 03:23:23 +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

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