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";
break;
case Device.Android:
FontFamily = "bwi-font.ttf#bwi-font";
FontFamily = "bwi-font.ttf";
break;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ namespace Bit.App.Utilities
public object Convert(object value, Type targetType, object parameter,
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,