PM-3349 PM-3350 MAUI Migration fix nullable exception bindings and AsyncCommand canExecute null exception

This commit is contained in:
Federico Maccaroni 2023-09-29 12:12:01 -03:00
parent 8ef9443b1e
commit b8f0747dd4
No known key found for this signature in database
GPG key ID: 5D233F8F2B034536
15 changed files with 136 additions and 121 deletions

View file

@ -16,7 +16,7 @@ namespace Bit.App.Controls
FontFamily = "bwi-font"; FontFamily = "bwi-font";
break; break;
case Device.Android: case Device.Android:
FontFamily = "bwi-font.ttf#bwi-font"; FontFamily = "bwi-font.ttf";
break; break;
} }

View file

@ -17,7 +17,7 @@ namespace Bit.App.Controls
FontFamily = "bwi-font"; FontFamily = "bwi-font";
break; break;
case Device.Android: case Device.Android:
FontFamily = "bwi-font.ttf#bwi-font"; FontFamily = "bwi-font.ttf";
break; break;
} }

View file

@ -15,7 +15,7 @@ namespace Bit.App.Controls
FontFamily = "Material Icons"; FontFamily = "Material Icons";
break; break;
case Device.Android: case Device.Android:
FontFamily = "MaterialIcons_Regular.ttf#Material Icons"; FontFamily = "MaterialIcons_Regular.ttf";
break; break;
} }
} }

View file

@ -14,7 +14,7 @@ namespace Bit.App.Controls
FontFamily = "Material Icons"; FontFamily = "Material Icons";
break; break;
case Device.Android: case Device.Android:
FontFamily = "MaterialIcons_Regular.ttf#Material Icons"; FontFamily = "MaterialIcons_Regular.ttf";
break; break;
} }
} }

View file

@ -1,22 +1,14 @@
using Microsoft.Maui.Controls; namespace Bit.App.Controls
using Microsoft.Maui;
namespace Bit.App.Controls
{ {
public class MonoEntry : Entry public class MonoEntry : Entry
{ {
public MonoEntry() public MonoEntry()
{ {
// 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 ANDROID
switch (Device.RuntimePlatform) FontFamily = "RobotoMono_Regular";
{ #elif IOS
case Device.iOS: FontFamily = "Menlo-Regular";
FontFamily = "Menlo-Regular"; #endif
break;
case Device.Android:
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
break;
}
} }
} }
} }

View file

@ -14,7 +14,7 @@ namespace Bit.App.Controls
FontFamily = "Menlo-Regular"; FontFamily = "Menlo-Regular";
break; break;
case Device.Android: case Device.Android:
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono"; FontFamily = "RobotoMono_Regular.ttf";
break; break;
} }
} }

View file

@ -14,7 +14,7 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<!-- <controls:ExtendedToolbarItem <controls:ExtendedToolbarItem
x:Name="_accountAvatar" x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}" IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}" Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
@ -23,11 +23,11 @@
UseOriginalImage="True" UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Account}" SemanticProperties.Description="{u:I18n Account}"
AutomationId="AccountIconButton" /> --> AutomationId="AccountIconButton" />
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/> <ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<!--<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
<StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="30" Padding="20, 50, 20, 0"> <StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="30" Padding="20, 50, 20, 0">
@ -122,11 +122,9 @@
</Label> </Label>
</StackLayout> </StackLayout>
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources>--> </ContentPage.Resources>
<Label Text="Lalala" /> <AbsoluteLayout
<!--<AbsoluteLayout
x:Name="_absLayout" x:Name="_absLayout"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"> HorizontalOptions="FillAndExpand">
@ -142,5 +140,5 @@
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}" MainPage="{Binding Source={x:Reference _page}}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/> BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>--> </AbsoluteLayout>
</pages:BaseContentPage> </pages:BaseContentPage>

View file

@ -32,7 +32,7 @@ namespace Bit.App.Pages
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync()); _vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
// await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
}; };
UpdateLogo(); UpdateLogo();
@ -43,7 +43,7 @@ namespace Bit.App.Pages
} }
if (_appOptions?.HideAccountSwitcher ?? false) if (_appOptions?.HideAccountSwitcher ?? false)
{ {
// ToolbarItems.Remove(_accountAvatar); ToolbarItems.Remove(_accountAvatar);
} }
} }
@ -56,8 +56,8 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
// _mainContent.Content = _mainLayout; _mainContent.Content = _mainLayout;
// _accountAvatar?.OnAppearing(); _accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false) if (!_appOptions?.HideAccountSwitcher ?? false)
{ {
@ -85,11 +85,11 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
{ {
// if (_accountListOverlay.IsVisible) if (_accountListOverlay.IsVisible)
// { {
// _accountListOverlay.HideAsync().FireAndForget(); _accountListOverlay.HideAsync().FireAndForget();
// return true; return true;
// } }
return false; return false;
} }
@ -97,12 +97,12 @@ namespace Bit.App.Pages
{ {
base.OnDisappearing(); base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage)); _broadcasterService.Unsubscribe(nameof(HomePage));
// _accountAvatar?.OnDisappearing(); _accountAvatar?.OnDisappearing();
} }
private void UpdateLogo() private void UpdateLogo()
{ {
// _logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png"; _logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
} }
private void Cancel_Clicked(object sender, EventArgs e) private void Cancel_Clicked(object sender, EventArgs e)
@ -141,7 +141,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync() private async Task StartEnvironmentAsync()
{ {
// await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
var page = new EnvironmentPage(); var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }

View file

@ -1,19 +1,15 @@
using System; using System.Windows.Input;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Controls; #nullable enable
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -345,7 +341,7 @@ namespace Bit.App.Pages
get => _usernameOptions.PlusAddressedEmail; get => _usernameOptions.PlusAddressedEmail;
set set
{ {
if (_usernameOptions.PlusAddressedEmail != value) if (_usernameOptions != null && _usernameOptions.PlusAddressedEmail != value)
{ {
_usernameOptions.PlusAddressedEmail = value; _usernameOptions.PlusAddressedEmail = value;
TriggerPropertyChanged(nameof(PlusAddressedEmail)); TriggerPropertyChanged(nameof(PlusAddressedEmail));
@ -364,7 +360,7 @@ namespace Bit.App.Pages
}); });
} }
public bool IsPolicyInEffect => _enforcedPolicyOptions.InEffect(); public bool IsPolicyInEffect => _enforcedPolicyOptions?.InEffect() == true;
public GeneratorType GeneratorTypeSelected public GeneratorType GeneratorTypeSelected
{ {
@ -375,6 +371,7 @@ namespace Bit.App.Pages
{ {
IsUsername = value == GeneratorType.Username; IsUsername = value == GeneratorType.Username;
TriggerPropertyChanged(nameof(GeneratorTypeSelected)); TriggerPropertyChanged(nameof(GeneratorTypeSelected));
TriggerPropertyChanged(nameof(UsernameTypeSelected));
SaveOptionsAsync().FireAndForget(); SaveOptionsAsync().FireAndForget();
SaveUsernameOptionsAsync().FireAndForget(); SaveUsernameOptionsAsync().FireAndForget();
} }
@ -398,10 +395,10 @@ namespace Bit.App.Pages
public UsernameType UsernameTypeSelected public UsernameType UsernameTypeSelected
{ {
get => _usernameOptions.Type; get => _usernameOptions?.Type ?? UsernameType.PlusAddressedEmail;
set set
{ {
if (_usernameOptions.Type != value) if (_usernameOptions != null && _usernameOptions.Type != value)
{ {
_usernameOptions.Type = value; _usernameOptions.Type = value;
Username = Constants.DefaultUsernameGenerated; Username = Constants.DefaultUsernameGenerated;
@ -415,10 +412,10 @@ namespace Bit.App.Pages
public ForwardedEmailServiceType ForwardedEmailServiceSelected public ForwardedEmailServiceType ForwardedEmailServiceSelected
{ {
get => _usernameOptions.ServiceType; get => _usernameOptions?.ServiceType ?? ForwardedEmailServiceType.None;
set set
{ {
if (_usernameOptions.ServiceType != value) if (_usernameOptions != null && _usernameOptions.ServiceType != value)
{ {
_usernameOptions.ServiceType = value; _usernameOptions.ServiceType = value;
Username = Constants.DefaultUsernameGenerated; Username = Constants.DefaultUsernameGenerated;
@ -434,10 +431,10 @@ namespace Bit.App.Pages
public string CatchAllEmailDomain public string CatchAllEmailDomain
{ {
get => _usernameOptions.CatchAllEmailDomain; get => _usernameOptions?.CatchAllEmailDomain;
set set
{ {
if (_usernameOptions.CatchAllEmailDomain != value) if (_usernameOptions != null && _usernameOptions.CatchAllEmailDomain != value)
{ {
_usernameOptions.CatchAllEmailDomain = value; _usernameOptions.CatchAllEmailDomain = value;
TriggerPropertyChanged(nameof(CatchAllEmailDomain)); TriggerPropertyChanged(nameof(CatchAllEmailDomain));
@ -450,6 +447,11 @@ namespace Bit.App.Pages
{ {
get get
{ {
if (_usernameOptions is null)
{
return null;
}
switch (ForwardedEmailServiceSelected) switch (ForwardedEmailServiceSelected)
{ {
case ForwardedEmailServiceType.AnonAddy: case ForwardedEmailServiceType.AnonAddy:
@ -468,6 +470,11 @@ namespace Bit.App.Pages
} }
set set
{ {
if (_usernameOptions is null)
{
return;
}
bool changed = false; bool changed = false;
switch (ForwardedEmailServiceSelected) switch (ForwardedEmailServiceSelected)
{ {
@ -548,10 +555,10 @@ namespace Bit.App.Pages
public string AddyIoDomainName public string AddyIoDomainName
{ {
get => _usernameOptions.AnonAddyDomainName; get => _usernameOptions?.AnonAddyDomainName;
set set
{ {
if (_usernameOptions.AnonAddyDomainName != value) if (_usernameOptions != null && _usernameOptions.AnonAddyDomainName != value)
{ {
_usernameOptions.AnonAddyDomainName = value; _usernameOptions.AnonAddyDomainName = value;
TriggerPropertyChanged(nameof(AddyIoDomainName)); TriggerPropertyChanged(nameof(AddyIoDomainName));
@ -562,10 +569,10 @@ namespace Bit.App.Pages
public bool CapitalizeRandomWordUsername public bool CapitalizeRandomWordUsername
{ {
get => _usernameOptions.CapitalizeRandomWordUsername; get => _usernameOptions?.CapitalizeRandomWordUsername == true;
set set
{ {
if (_usernameOptions.CapitalizeRandomWordUsername != value) if (_usernameOptions != null && _usernameOptions.CapitalizeRandomWordUsername != value)
{ {
_usernameOptions.CapitalizeRandomWordUsername = value; _usernameOptions.CapitalizeRandomWordUsername = value;
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername)); TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
@ -576,10 +583,10 @@ namespace Bit.App.Pages
public bool IncludeNumberRandomWordUsername public bool IncludeNumberRandomWordUsername
{ {
get => _usernameOptions.IncludeNumberRandomWordUsername; get => _usernameOptions?.IncludeNumberRandomWordUsername == true;
set set
{ {
if (_usernameOptions.IncludeNumberRandomWordUsername != value) if (_usernameOptions != null && _usernameOptions.IncludeNumberRandomWordUsername != value)
{ {
_usernameOptions.IncludeNumberRandomWordUsername = value; _usernameOptions.IncludeNumberRandomWordUsername = value;
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername)); TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
@ -590,10 +597,10 @@ namespace Bit.App.Pages
public UsernameEmailType PlusAddressedEmailTypeSelected public UsernameEmailType PlusAddressedEmailTypeSelected
{ {
get => _usernameOptions.PlusAddressedEmailType; get => _usernameOptions?.PlusAddressedEmailType ?? UsernameEmailType.Random;
set set
{ {
if (_usernameOptions.PlusAddressedEmailType != value) if (_usernameOptions != null && _usernameOptions.PlusAddressedEmailType != value)
{ {
_usernameOptions.PlusAddressedEmailType = value; _usernameOptions.PlusAddressedEmailType = value;
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected)); TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
@ -604,10 +611,10 @@ namespace Bit.App.Pages
public UsernameEmailType CatchAllEmailTypeSelected public UsernameEmailType CatchAllEmailTypeSelected
{ {
get => _usernameOptions.CatchAllEmailType; get => _usernameOptions?.CatchAllEmailType ?? UsernameEmailType.Random;
set set
{ {
if (_usernameOptions.CatchAllEmailType != value) if (_usernameOptions != null && _usernameOptions.CatchAllEmailType != value)
{ {
_usernameOptions.CatchAllEmailType = value; _usernameOptions.CatchAllEmailType = value;
TriggerPropertyChanged(nameof(CatchAllEmailTypeSelected)); TriggerPropertyChanged(nameof(CatchAllEmailTypeSelected));

View file

@ -258,10 +258,11 @@
<StackLayout.GestureRecognizers> <StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ToggleOptionsCommand}" /> <TapGestureRecognizer Command="{Binding ToggleOptionsCommand}" />
</StackLayout.GestureRecognizers> </StackLayout.GestureRecognizers>
<Button <controls:CustomLabel
Text="{u:I18n Options}" Text="{u:I18n Options}"
x:Name="_btnOptions" x:Name="_btnOptions"
StyleClass="box-row-button" StyleClass="box-row-button"
VerticalTextAlignment="Center"
TextColor="{DynamicResource PrimaryColor}" TextColor="{DynamicResource PrimaryColor}"
Margin="0" Margin="0"
AutomationProperties.IsInAccessibleTree="False" AutomationProperties.IsInAccessibleTree="False"
@ -408,7 +409,7 @@
AutomationId="SendMaxAccessCountEntry" /> AutomationId="SendMaxAccessCountEntry" />
<controls:ExtendedStepper <controls:ExtendedStepper
x:Name="_maxAccessCountStepper" x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}" Value="{Binding MaxAccessCount, TargetNullValue=0}"
Maximum="999999999" Maximum="999999999"
IsEnabled="{Binding SendEnabled}" IsEnabled="{Binding SendEnabled}"
Margin="10,0,0,0" Margin="10,0,0,0"

View file

@ -141,7 +141,7 @@
StyleClass="box-label"/> StyleClass="box-label"/>
<Entry <Entry
x:Name="_loginUsernameEntry" x:Name="_loginUsernameEntry"
Text="{Binding Cipher.Login.Username}" Text="{Binding Cipher.Login.Username, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
Grid.Row="1" Grid.Row="1"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
@ -175,7 +175,7 @@
Grid.Column="0" /> Grid.Column="0" />
<controls:MonoEntry <controls:MonoEntry
x:Name="_loginPasswordEntry" x:Name="_loginPasswordEntry"
Text="{Binding Cipher.Login.Password}" Text="{Binding Cipher.Login.Password, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -183,7 +183,7 @@
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}" IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False" IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" IsTextPredictionEnabled="False"
IsEnabled="{Binding Cipher.ViewPassword}" IsEnabled="{Binding Cipher.ViewPassword, FallbackValue=False}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Password}" SemanticProperties.Description="{u:I18n Password}"
AutomationId="LoginPasswordEntry" /> AutomationId="LoginPasswordEntry" />
@ -196,7 +196,7 @@
Grid.RowSpan="2" Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n CheckPassword}" SemanticProperties.Description="{u:I18n CheckPassword}"
IsVisible="{Binding Cipher.ViewPassword}" IsVisible="{Binding Cipher.ViewPassword, FallbackValue=False}"
AutomationId="CheckPasswordButton" /> AutomationId="CheckPasswordButton" />
<controls:IconButton <controls:IconButton
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
@ -208,7 +208,7 @@
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n ToggleVisibility}" SemanticProperties.Description="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" IsVisible="{Binding Cipher.ViewPassword, FallbackValue=False}"
AutomationId="ViewPasswordButton" /> AutomationId="ViewPasswordButton" />
<controls:IconButton <controls:IconButton
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
@ -237,7 +237,7 @@
<Grid StyleClass="box-row, box-row-input"> <Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -253,8 +253,6 @@
IsVisible="{Binding HasTotpValue, Converter={StaticResource inverseBool}}" IsVisible="{Binding HasTotpValue, Converter={StaticResource inverseBool}}"
Margin="0,5,0,0" Margin="0,5,0,0"
StyleClass="btn-icon-row" StyleClass="btn-icon-row"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Padding="0" Padding="0"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -272,12 +270,12 @@
</Frame> </Frame>
<controls:MonoEntry <controls:MonoEntry
x:Name="_loginTotpEntry" x:Name="_loginTotpEntry"
Text="{Binding Cipher.Login.Totp}" Text="{Binding Cipher.Login.Totp, FallbackValue=''}"
IsSpellCheckEnabled="False" IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" IsTextPredictionEnabled="False"
IsVisible="{Binding HasTotpValue}" IsVisible="{Binding HasTotpValue}"
IsPassword="{Binding Cipher.ViewPassword, Converter={StaticResource inverseBool}}" IsPassword="{Binding Cipher.ViewPassword, Converter={StaticResource inverseBool}, FallbackValue=False}"
IsEnabled="{Binding Cipher.ViewPassword}" IsEnabled="{Binding Cipher.ViewPassword, FallbackValue=False}"
StyleClass="box-value" StyleClass="box-value"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -316,7 +314,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_cardholderNameEntry" x:Name="_cardholderNameEntry"
Text="{Binding Cipher.Card.CardholderName}" Text="{Binding Cipher.Card.CardholderName, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationId="CardholderNameEntry" /> AutomationId="CardholderNameEntry" />
</StackLayout> </StackLayout>
@ -336,7 +334,7 @@
Grid.Column="0" /> Grid.Column="0" />
<controls:MonoEntry <controls:MonoEntry
x:Name="_cardNumberEntry" x:Name="_cardNumberEntry"
Text="{Binding Cipher.Card.Number}" Text="{Binding Cipher.Card.Number, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -385,7 +383,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_cardExpYearEntry" x:Name="_cardExpYearEntry"
Text="{Binding Cipher.Card.ExpYear}" Text="{Binding Cipher.Card.ExpYear, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
Keyboard="Numeric" Keyboard="Numeric"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
@ -408,7 +406,7 @@
Grid.Column="0" /> Grid.Column="0" />
<controls:MonoEntry <controls:MonoEntry
x:Name="_cardCodeEntry" x:Name="_cardCodeEntry"
Text="{Binding Cipher.Card.Code}" Text="{Binding Cipher.Card.Code, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@ -449,7 +447,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityFirstNameEntry" x:Name="_identityFirstNameEntry"
Text="{Binding Cipher.Identity.FirstName}" Text="{Binding Cipher.Identity.FirstName, FallbackValue=''}"
StyleClass="box-value,capitalize-word-input" StyleClass="box-value,capitalize-word-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n FirstName}" SemanticProperties.Description="{u:I18n FirstName}"
@ -461,7 +459,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityMiddleNameEntry" x:Name="_identityMiddleNameEntry"
Text="{Binding Cipher.Identity.MiddleName}" Text="{Binding Cipher.Identity.MiddleName, FallbackValue=''}"
StyleClass="box-value,capitalize-word-input" StyleClass="box-value,capitalize-word-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n MiddleName}" SemanticProperties.Description="{u:I18n MiddleName}"
@ -473,7 +471,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityLastNameEntry" x:Name="_identityLastNameEntry"
Text="{Binding Cipher.Identity.LastName}" Text="{Binding Cipher.Identity.LastName, FallbackValue=''}"
StyleClass="box-value,capitalize-word-input" StyleClass="box-value,capitalize-word-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n LastName}" SemanticProperties.Description="{u:I18n LastName}"
@ -485,7 +483,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityUsernameEntry" x:Name="_identityUsernameEntry"
Text="{Binding Cipher.Identity.Username}" Text="{Binding Cipher.Identity.Username, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Username}" SemanticProperties.Description="{u:I18n Username}"
@ -497,7 +495,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityCompanyEntry" x:Name="_identityCompanyEntry"
Text="{Binding Cipher.Identity.Company}" Text="{Binding Cipher.Identity.Company, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Company}" SemanticProperties.Description="{u:I18n Company}"
@ -509,7 +507,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identitySsnEntry" x:Name="_identitySsnEntry"
Text="{Binding Cipher.Identity.SSN}" Text="{Binding Cipher.Identity.SSN, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n SSN}" SemanticProperties.Description="{u:I18n SSN}"
@ -521,7 +519,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityPassportNumberEntry" x:Name="_identityPassportNumberEntry"
Text="{Binding Cipher.Identity.PassportNumber}" Text="{Binding Cipher.Identity.PassportNumber, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n PassportNumber}" SemanticProperties.Description="{u:I18n PassportNumber}"
@ -533,7 +531,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityLicenseNumberEntry" x:Name="_identityLicenseNumberEntry"
Text="{Binding Cipher.Identity.LicenseNumber}" Text="{Binding Cipher.Identity.LicenseNumber, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n LicenseNumber}" SemanticProperties.Description="{u:I18n LicenseNumber}"
@ -546,7 +544,7 @@
<Entry <Entry
x:Name="_identityEmailEntry" x:Name="_identityEmailEntry"
Keyboard="Email" Keyboard="Email"
Text="{Binding Cipher.Identity.Email}" Text="{Binding Cipher.Identity.Email, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Email}" SemanticProperties.Description="{u:I18n Email}"
@ -558,7 +556,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityPhoneEntry" x:Name="_identityPhoneEntry"
Text="{Binding Cipher.Identity.Phone}" Text="{Binding Cipher.Identity.Phone, FallbackValue=''}"
Keyboard="Telephone" Keyboard="Telephone"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
@ -571,7 +569,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityAddress1Entry" x:Name="_identityAddress1Entry"
Text="{Binding Cipher.Identity.Address1}" Text="{Binding Cipher.Identity.Address1, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Address1}" SemanticProperties.Description="{u:I18n Address1}"
@ -583,7 +581,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityAddress2Entry" x:Name="_identityAddress2Entry"
Text="{Binding Cipher.Identity.Address2}" Text="{Binding Cipher.Identity.Address2, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Address2}" SemanticProperties.Description="{u:I18n Address2}"
@ -595,7 +593,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityAddress3Entry" x:Name="_identityAddress3Entry"
Text="{Binding Cipher.Identity.Address3}" Text="{Binding Cipher.Identity.Address3, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Address3}" SemanticProperties.Description="{u:I18n Address3}"
@ -607,7 +605,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityCityEntry" x:Name="_identityCityEntry"
Text="{Binding Cipher.Identity.City}" Text="{Binding Cipher.Identity.City, FallbackValue=''}"
StyleClass="box-value,capitalize-sentence-input" StyleClass="box-value,capitalize-sentence-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n CityTown}" SemanticProperties.Description="{u:I18n CityTown}"
@ -619,7 +617,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityStateEntry" x:Name="_identityStateEntry"
Text="{Binding Cipher.Identity.State}" Text="{Binding Cipher.Identity.State, FallbackValue=''}"
StyleClass="box-value,capitalize-sentence-input" StyleClass="box-value,capitalize-sentence-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n StateProvince}" SemanticProperties.Description="{u:I18n StateProvince}"
@ -631,7 +629,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityPostalCodeEntry" x:Name="_identityPostalCodeEntry"
Text="{Binding Cipher.Identity.PostalCode}" Text="{Binding Cipher.Identity.PostalCode, FallbackValue=''}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n ZipPostalCode}" SemanticProperties.Description="{u:I18n ZipPostalCode}"
@ -643,7 +641,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Entry <Entry
x:Name="_identityCountryEntry" x:Name="_identityCountryEntry"
Text="{Binding Cipher.Identity.Country}" Text="{Binding Cipher.Identity.Country, FallbackValue=''}"
StyleClass="box-value,capitalize-sentence-input" StyleClass="box-value,capitalize-sentence-input"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Country}" SemanticProperties.Description="{u:I18n Country}"
@ -656,13 +654,13 @@
<Label Text="{u:I18n URIs, Header=True}" <Label Text="{u:I18n URIs, Header=True}"
StyleClass="box-header, box-header-platform" /> StyleClass="box-header, box-header-platform" />
</StackLayout> </StackLayout>
<controls:RepeaterView ItemsSource="{Binding Uris}"> <StackLayout BindableLayout.ItemsSource="{Binding Uris}">
<controls:RepeaterView.ItemTemplate> <BindableLayout.ItemTemplate>
<DataTemplate x:DataType="views:LoginUriView"> <DataTemplate x:DataType="views:LoginUriView">
<Grid StyleClass="box-row, box-row-input" AutomationId="UriListGrid" > <Grid StyleClass="box-row, box-row-input" AutomationId="UriListGrid" >
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -695,8 +693,8 @@
AutomationId="LoginUriOptionsButton" /> AutomationId="LoginUriOptionsButton" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</controls:RepeaterView.ItemTemplate> </BindableLayout.ItemTemplate>
</controls:RepeaterView> </StackLayout>
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row" <Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
Clicked="NewUri_Clicked" Clicked="NewUri_Clicked"
AutomationId="LoginAddNewUriButton"></Button> AutomationId="LoginAddNewUriButton"></Button>
@ -723,7 +721,7 @@
StyleClass="box-label-regular" StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding Cipher.Favorite}" IsToggled="{Binding Cipher.Favorite, FallbackValue=False}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End"
AutomationId="ItemFavoriteToggle" /> AutomationId="ItemFavoriteToggle" />
@ -741,7 +739,7 @@
SemanticProperties.Description="{u:I18n MasterPasswordRePromptHelp}" SemanticProperties.Description="{u:I18n MasterPasswordRePromptHelp}"
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding PasswordPrompt}" IsToggled="{Binding PasswordPrompt, Mode=OneWay}"
Toggled="PasswordPrompt_Toggled" Toggled="PasswordPrompt_Toggled"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End"
@ -760,7 +758,7 @@
AutoSize="TextChanges" AutoSize="TextChanges"
StyleClass="box-value" StyleClass="box-value"
effects:ScrollEnabledEffect.IsScrollEnabled="false" effects:ScrollEnabledEffect.IsScrollEnabled="false"
Text="{Binding Cipher.Notes}" Text="{Binding Cipher.Notes, FallbackValue=''}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{u:I18n Notes}" SemanticProperties.Description="{u:I18n Notes}"
AutomationId="ItemNotesEntry"> AutomationId="ItemNotesEntry">
@ -799,7 +797,7 @@
StyleClass="box-label" /> StyleClass="box-label" />
<Picker <Picker
x:Name="_ownershipPicker" x:Name="_ownershipPicker"
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}" ItemsSource="{Binding OwnershipOptions, Mode=OneWay}"
SelectedIndex="{Binding OwnershipSelectedIndex}" SelectedIndex="{Binding OwnershipSelectedIndex}"
StyleClass="box-value" StyleClass="box-value"
AutomationId="ItemOwnershipPicker" /> AutomationId="ItemOwnershipPicker" />

View file

@ -288,7 +288,7 @@ namespace Bit.App.Pages
nameof(ShowCollections) nameof(ShowCollections)
}); });
} }
public bool ShowCollections => (!EditMode || CloneMode) && Cipher.OrganizationId != null; public bool ShowCollections => (!EditMode || CloneMode) && Cipher?.OrganizationId != null;
public bool EditMode => !string.IsNullOrWhiteSpace(CipherId); public bool EditMode => !string.IsNullOrWhiteSpace(CipherId);
public bool ShowOwnershipOptions => !EditMode || CloneMode; public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal; public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal;
@ -298,15 +298,15 @@ namespace Bit.App.Pages
public bool IsIdentity => Cipher?.Type == CipherType.Identity; public bool IsIdentity => Cipher?.Type == CipherType.Identity;
public bool IsCard => Cipher?.Type == CipherType.Card; public bool IsCard => Cipher?.Type == CipherType.Card;
public bool IsSecureNote => Cipher?.Type == CipherType.SecureNote; public bool IsSecureNote => Cipher?.Type == CipherType.SecureNote;
public bool ShowUris => IsLogin && Cipher.Login.HasUris; public bool ShowUris => IsLogin && Cipher != null && Cipher.Login.HasUris;
public bool ShowAttachments => Cipher.HasAttachments; public bool ShowAttachments => Cipher != null && Cipher.HasAttachments;
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public int PasswordFieldColSpan => Cipher.ViewPassword ? 1 : 4; public int PasswordFieldColSpan => Cipher != null && Cipher.ViewPassword ? 1 : 4;
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2; public int TotpColumnSpan => Cipher != null && Cipher.ViewPassword ? 1 : 2;
public bool AllowPersonal { get; set; } public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None; public bool PasswordPrompt => Cipher != null && Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp); public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}"; public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
@ -459,6 +459,12 @@ namespace Bit.App.Pages
} }
_previousCipherId = CipherId; _previousCipherId = CipherId;
MainThread.BeginInvokeOnMainThread(() =>
{
TriggerPropertyChanged(nameof(OwnershipOptions));
TriggerPropertyChanged(nameof(OwnershipSelectedIndex));
});
return true; return true;
} }
@ -892,7 +898,7 @@ namespace Bit.App.Pages
private void TriggerCipherChanged() private void TriggerCipherChanged()
{ {
TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged); MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged));
} }
private async Task CopyTotpClipboardAsync() private async Task CopyTotpClipboardAsync()

View file

@ -198,11 +198,14 @@ namespace Bit.App.Pages
Text = string.Format("{0}:", AppResources.PasswordHistory), Text = string.Format("{0}:", AppResources.PasswordHistory),
FontAttributes = FontAttributes.Bold FontAttributes = FontAttributes.Bold
}); });
fs.Spans.Add(new Span if (Cipher?.PasswordHistory != null)
{ {
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()), fs.Spans.Add(new Span
TextColor = ThemeManager.GetResourceColor("PrimaryColor") {
}); Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
});
}
return fs; return fs;
} }
} }

View file

@ -27,7 +27,12 @@ namespace Bit.App.Utilities
} }
} }
_relayCommand = new AsyncRelayCommand(doAsync, canExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); var safeCanExecute = canExecute;
if (canExecute is null)
{
safeCanExecute = () => true;
}
_relayCommand = new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None);
} }
public event EventHandler CanExecuteChanged; public event EventHandler CanExecuteChanged;
@ -58,7 +63,12 @@ namespace Bit.App.Utilities
} }
} }
_relayCommand = new AsyncRelayCommand<T>(doAsync, canExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); var safeCanExecute = canExecute;
if (canExecute is null)
{
safeCanExecute = _ => true;
}
_relayCommand = new AsyncRelayCommand<T>(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None);
} }
public event EventHandler CanExecuteChanged; public event EventHandler CanExecuteChanged;

View file

@ -12,7 +12,7 @@ namespace Bit.App.Utilities
public object Convert(object value, Type targetType, object parameter, public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) System.Globalization.CultureInfo culture)
{ {
return EnumHelper.GetLocalizedValue(value, value.GetType()); return value != null ? EnumHelper.GetLocalizedValue(value, value.GetType()) : string.Empty;
} }
public object ConvertBack(object value, Type targetType, object parameter, public object ConvertBack(object value, Type targetType, object parameter,