mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +03:00
view page login info with copy buttons
This commit is contained in:
parent
0625f313dc
commit
baf77eb3a3
9 changed files with 350 additions and 8 deletions
21
src/App/Controls/FaButton.cs
Normal file
21
src/App/Controls/FaButton.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public class FaButton : Button
|
||||||
|
{
|
||||||
|
public FaButton()
|
||||||
|
{
|
||||||
|
Padding = 0;
|
||||||
|
switch(Device.RuntimePlatform)
|
||||||
|
{
|
||||||
|
case Device.iOS:
|
||||||
|
FontFamily = "FontAwesome";
|
||||||
|
break;
|
||||||
|
case Device.Android:
|
||||||
|
FontFamily = "FontAwesome.ttf#FontAwesome";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
x:Class="Bit.App.Pages.ViewPage"
|
x:Class="Bit.App.Pages.ViewPage"
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
x:DataType="pages:ViewPageViewModel"
|
x:DataType="pages:ViewPageViewModel"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
<ContentPage.BindingContext>
|
<ContentPage.BindingContext>
|
||||||
|
@ -17,6 +18,10 @@
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
|
<StackLayout StyleClass="box-row-header">
|
||||||
|
<Label Text="{u:I18n ItemInformation}"
|
||||||
|
StyleClass="box-header, box-header-platform" />
|
||||||
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Name}"
|
Text="{u:I18n Name}"
|
||||||
|
@ -24,6 +29,106 @@
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Name, Mode=OneWay}"
|
Text="{Binding Cipher.Name, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
<StackLayout IsVisible="{Binding IsLogin}">
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Username}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding Cipher.Login.Username, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<controls:FaButton StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text=""
|
||||||
|
Command="{Binding CopyCommand}"
|
||||||
|
CommandParameter="LoginUsername"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2" />
|
||||||
|
</Grid>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Password}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding Cipher.Login.MaskedPassword, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<controls:FaButton StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text=""
|
||||||
|
Command="{Binding CopyCommand}"
|
||||||
|
CommandParameter="LoginPassword"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2" />
|
||||||
|
</Grid>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n VerificationCodeTotp}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding TotpCodeFormatted, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding TotpSec, Mode=OneWay}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
VerticalOptions="CenterAndExpand"/>
|
||||||
|
<controls:FaButton StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text=""
|
||||||
|
Command="{Binding CopyCommand}"
|
||||||
|
CommandParameter="LoginTotp"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Grid.RowSpan="2" />
|
||||||
|
</Grid>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout IsVisible="{Binding IsCard}">
|
||||||
|
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout IsVisible="{Binding IsIdentity}">
|
||||||
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|
|
@ -49,6 +49,7 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
_broadcasterService.Unsubscribe(nameof(ViewPage));
|
_broadcasterService.Unsubscribe(nameof(ViewPage));
|
||||||
|
_vm.CleanUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
|
@ -12,39 +14,167 @@ namespace Bit.App.Pages
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly ITotpService _totpService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private CipherView _cipher;
|
private CipherView _cipher;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
|
private string _totpCode;
|
||||||
|
private string _totpCodeFormatted;
|
||||||
|
private string _totpSec;
|
||||||
|
private bool _totpLow;
|
||||||
|
private DateTime? _totpInterval = null;
|
||||||
|
|
||||||
public ViewPageViewModel()
|
public ViewPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||||
|
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
CopyCommand = new Command<string>(CopyAsync);
|
||||||
|
|
||||||
PageTitle = AppResources.ViewItem;
|
PageTitle = AppResources.ViewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Command CopyCommand { get; set; }
|
||||||
public string CipherId { get; set; }
|
public string CipherId { get; set; }
|
||||||
public CipherView Cipher
|
public CipherView Cipher
|
||||||
{
|
{
|
||||||
get => _cipher;
|
get => _cipher;
|
||||||
set => SetProperty(ref _cipher, value);
|
set => SetProperty(ref _cipher, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(IsLogin),
|
||||||
|
nameof(IsIdentity),
|
||||||
|
nameof(IsCard),
|
||||||
|
nameof(IsSecureNote)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public bool CanAccessPremium
|
public bool CanAccessPremium
|
||||||
{
|
{
|
||||||
get => _canAccessPremium;
|
get => _canAccessPremium;
|
||||||
set => SetProperty(ref _canAccessPremium, value);
|
set => SetProperty(ref _canAccessPremium, value);
|
||||||
}
|
}
|
||||||
|
public bool IsLogin => _cipher?.Type == Core.Enums.CipherType.Login;
|
||||||
|
public bool IsIdentity => _cipher?.Type == Core.Enums.CipherType.Identity;
|
||||||
|
public bool IsCard => _cipher?.Type == Core.Enums.CipherType.Card;
|
||||||
|
public bool IsSecureNote => _cipher?.Type == Core.Enums.CipherType.SecureNote;
|
||||||
|
public string TotpCodeFormatted
|
||||||
|
{
|
||||||
|
get => _totpCodeFormatted;
|
||||||
|
set => SetProperty(ref _totpCodeFormatted, value);
|
||||||
|
}
|
||||||
|
public string TotpSec
|
||||||
|
{
|
||||||
|
get => _totpSec;
|
||||||
|
set => SetProperty(ref _totpSec, value);
|
||||||
|
}
|
||||||
|
public bool TotpLow
|
||||||
|
{
|
||||||
|
get => _totpLow;
|
||||||
|
set => SetProperty(ref _totpLow, value);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
{
|
{
|
||||||
// TODO: Cleanup
|
CleanUp();
|
||||||
|
|
||||||
var cipher = await _cipherService.GetAsync(CipherId);
|
var cipher = await _cipherService.GetAsync(CipherId);
|
||||||
Cipher = await cipher.DecryptAsync();
|
Cipher = await cipher.DecryptAsync();
|
||||||
CanAccessPremium = await _userService.CanAccessPremiumAsync();
|
CanAccessPremium = await _userService.CanAccessPremiumAsync();
|
||||||
|
|
||||||
// TODO: Totp
|
if(Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
||||||
|
(Cipher.OrganizationUseTotp || CanAccessPremium))
|
||||||
|
{
|
||||||
|
await TotpUpdateCodeAsync();
|
||||||
|
var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||||
|
await TotpTickAsync(interval);
|
||||||
|
_totpInterval = DateTime.UtcNow;
|
||||||
|
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
|
||||||
|
{
|
||||||
|
if(_totpInterval == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var task = TotpTickAsync(interval);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp()
|
||||||
|
{
|
||||||
|
_totpInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TotpUpdateCodeAsync()
|
||||||
|
{
|
||||||
|
if(Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
|
||||||
|
{
|
||||||
|
_totpInterval = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
|
||||||
|
if(_totpCode != null)
|
||||||
|
{
|
||||||
|
if(_totpCode.Length > 4)
|
||||||
|
{
|
||||||
|
var half = (int)Math.Floor(_totpCode.Length / 2M);
|
||||||
|
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
|
||||||
|
_totpCode.Substring(half));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TotpCodeFormatted = _totpCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TotpCodeFormatted = null;
|
||||||
|
_totpInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TotpTickAsync(int intervalSeconds)
|
||||||
|
{
|
||||||
|
var epoc = CoreHelpers.EpocUtcNow() / 1000;
|
||||||
|
var mod = epoc % intervalSeconds;
|
||||||
|
var totpSec = intervalSeconds - mod;
|
||||||
|
TotpSec = totpSec.ToString();
|
||||||
|
TotpLow = totpSec < 7;
|
||||||
|
if(mod == 0)
|
||||||
|
{
|
||||||
|
await TotpUpdateCodeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CopyAsync(string id)
|
||||||
|
{
|
||||||
|
string text = null;
|
||||||
|
string name = null;
|
||||||
|
if(id == "LoginUsername")
|
||||||
|
{
|
||||||
|
text = Cipher.Login.Username;
|
||||||
|
name = AppResources.Username;
|
||||||
|
}
|
||||||
|
else if(id == "LoginPassword")
|
||||||
|
{
|
||||||
|
text = Cipher.Login.Password;
|
||||||
|
name = AppResources.Password;
|
||||||
|
}
|
||||||
|
else if(id == "LoginTotp")
|
||||||
|
{
|
||||||
|
text = _totpCode;
|
||||||
|
name = AppResources.VerificationCodeTotp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(text != null)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.CopyToClipboardAsync(text);
|
||||||
|
if(!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,15 @@
|
||||||
<Style TargetType="Grid"
|
<Style TargetType="Grid"
|
||||||
Class="list-row-platform">
|
Class="list-row-platform">
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Box -->
|
||||||
|
|
||||||
|
<Style TargetType="Button"
|
||||||
|
ApplyToDerivedTypes="True"
|
||||||
|
Class="box-row-button-platform">
|
||||||
|
<Setter Property="WidthRequest"
|
||||||
|
Value="40" />
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="28" />
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -62,4 +62,61 @@
|
||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{StaticResource MutedColor}" />
|
Value="{StaticResource MutedColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Box -->
|
||||||
|
|
||||||
|
<Style TargetType="StackLayout"
|
||||||
|
Class="box">
|
||||||
|
<Setter Property="Padding"
|
||||||
|
Value="10, 0" />
|
||||||
|
<Setter Property="Spacing"
|
||||||
|
Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Grid"
|
||||||
|
Class="box-row">
|
||||||
|
<Setter Property="Padding"
|
||||||
|
Value="0, 10" />
|
||||||
|
<Setter Property="RowSpacing"
|
||||||
|
Value="0" />
|
||||||
|
<Setter Property="ColumnSpacing"
|
||||||
|
Value="10" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="StackLayout"
|
||||||
|
Class="box-row">
|
||||||
|
<Setter Property="Padding"
|
||||||
|
Value="0, 10" />
|
||||||
|
<Setter Property="Spacing"
|
||||||
|
Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Button"
|
||||||
|
ApplyToDerivedTypes="True"
|
||||||
|
Class="box-row-button">
|
||||||
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="Transparent" />
|
||||||
|
<Setter Property="Padding"
|
||||||
|
Value="0" />
|
||||||
|
<Setter Property="TextColor"
|
||||||
|
Value="{StaticResource PrimaryColor}" />
|
||||||
|
<Setter Property="HorizontalOptions"
|
||||||
|
Value="End" />
|
||||||
|
<Setter Property="VerticalOptions"
|
||||||
|
Value="CenterAndExpand" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="BoxView"
|
||||||
|
Class="box-row-separator">
|
||||||
|
<Setter Property="HeightRequest"
|
||||||
|
Value="1" />
|
||||||
|
<Setter Property="Color"
|
||||||
|
Value="{StaticResource BoxBorderColor}" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="box-label">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="Small" />
|
||||||
|
<Setter Property="TextColor"
|
||||||
|
Value="{StaticResource MutedColor}" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="box-value">
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -10,5 +10,10 @@
|
||||||
<Color x:Key="WarningColor">#bf7e16</Color>
|
<Color x:Key="WarningColor">#bf7e16</Color>
|
||||||
<Color x:Key="MutedColor">#777777</Color>
|
<Color x:Key="MutedColor">#777777</Color>
|
||||||
|
|
||||||
|
<Color x:Key="BorderColor">#dddddd</Color>
|
||||||
|
|
||||||
|
<Color x:Key="BoxBorderColor">#f0f0f0</Color>
|
||||||
|
|
||||||
|
<Color x:Key="ListItemBorderColor">#f0f0f0</Color>
|
||||||
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color>
|
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -10,5 +10,10 @@
|
||||||
<Color x:Key="WarningColor">#bf7e16</Color>
|
<Color x:Key="WarningColor">#bf7e16</Color>
|
||||||
<Color x:Key="MutedColor">#777777</Color>
|
<Color x:Key="MutedColor">#777777</Color>
|
||||||
|
|
||||||
|
<Color x:Key="BorderColor">#dddddd</Color>
|
||||||
|
|
||||||
|
<Color x:Key="BoxBorderColor">#dddddd</Color>
|
||||||
|
|
||||||
|
<Color x:Key="ListItemBorderColor">#f0f0f0</Color>
|
||||||
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color>
|
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Bit.Core.Utilities
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
protected bool SetProperty<T>(ref T backingStore, T value, Action onChanged = null,
|
protected bool SetProperty<T>(ref T backingStore, T value, Action onChanged = null,
|
||||||
[CallerMemberName]string propertyName = "")
|
[CallerMemberName]string propertyName = "", string[] additionalPropertyNames = null)
|
||||||
{
|
{
|
||||||
if(EqualityComparer<T>.Default.Equals(backingStore, value))
|
if(EqualityComparer<T>.Default.Equals(backingStore, value))
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,13 @@ namespace Bit.Core.Utilities
|
||||||
|
|
||||||
backingStore = value;
|
backingStore = value;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
if(PropertyChanged != null && additionalPropertyNames != null)
|
||||||
|
{
|
||||||
|
foreach(var prop in additionalPropertyNames)
|
||||||
|
{
|
||||||
|
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
onChanged?.Invoke();
|
onChanged?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue