mirror of
https://github.com/bitwarden/mobile
synced 2025-01-28 01:09:43 +01:00
[EC-528] Refactor Custom Fields into separate components (#1662)
* Refactored CustomFields to stop using RepeaterView and use BindableLayout and divided the different types on different files and added a factory to create them * Fix formatting
This commit is contained in:
parent
2016eadb0d
commit
b7048de2a1
@ -48,6 +48,7 @@ namespace Bit.Droid
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||
Constants.AndroidAllClearCipherCacheKeys);
|
||||
InitializeAppSetup();
|
||||
|
||||
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||
@ -193,5 +194,12 @@ namespace Bit.Droid
|
||||
{
|
||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||
}
|
||||
|
||||
private void InitializeAppSetup()
|
||||
{
|
||||
var appSetup = new AppSetup();
|
||||
appSetup.InitializeServicesLastChance();
|
||||
ServiceContainer.Register<IAppSetup>("appSetup", appSetup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -127,6 +128,12 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Lists\" />
|
||||
<Folder Include="Lists\ItemLayouts\" />
|
||||
<Folder Include="Lists\DataTemplateSelectors\" />
|
||||
<Folder Include="Lists\ItemLayouts\CustomFields\" />
|
||||
<Folder Include="Lists\ItemViewModels\" />
|
||||
<Folder Include="Lists\ItemViewModels\CustomFields\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
<Folder Include="Utilities\AccountManagement\" />
|
||||
<Folder Include="Controls\DateTime\" />
|
||||
@ -412,6 +419,12 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Behaviors\" />
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
<None Remove="Lists\" />
|
||||
<None Remove="Lists\DataTemplates\" />
|
||||
<None Remove="Lists\DataTemplateSelectors\" />
|
||||
<None Remove="Lists\DataTemplates\CustomFields\" />
|
||||
<None Remove="Lists\ItemViewModels\" />
|
||||
<None Remove="Lists\ItemViewModels\CustomFields\" />
|
||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||
<None Remove="Utilities\AccountManagement\" />
|
||||
<None Remove="Controls\DateTime\" />
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
[Obsolete]
|
||||
public class RepeaterView : StackLayout
|
||||
{
|
||||
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
|
||||
|
@ -0,0 +1,28 @@
|
||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.DataTemplateSelectors
|
||||
{
|
||||
public class CustomFieldItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate TextTemplate { get; set; }
|
||||
public DataTemplate BooleanTemplate { get; set; }
|
||||
public DataTemplate LinkedTemplate { get; set; }
|
||||
public DataTemplate HiddenTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case BooleanCustomFieldItemViewModel _:
|
||||
return BooleanTemplate;
|
||||
case LinkedCustomFieldItemViewModel _:
|
||||
return LinkedTemplate;
|
||||
case HiddenCustomFieldItemViewModel _:
|
||||
return HiddenTemplate;
|
||||
default:
|
||||
return TextTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<StackLayout
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.BooleanCustomFieldItemLayout"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:BooleanCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
StyleClass="box-value"
|
||||
VerticalOptions="FillAndExpand"
|
||||
VerticalTextAlignment="Center"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding BooleanValue, Mode=OneWay, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Checkbox}}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0, 5, 0, 0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
<Switch
|
||||
IsToggled="{Binding BooleanValue}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class BooleanCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public BooleanCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.HiddenCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:HiddenCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<StackLayout
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Converter={StaticResource inverseBool}}">
|
||||
<controls:MonoLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
IsVisible="{Binding ShowHiddenValue}" />
|
||||
<controls:MonoLabel
|
||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
<controls:MonoEntry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
|
||||
IsEnabled="{Binding ShowViewHidden}"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False">
|
||||
<Entry.Keyboard>
|
||||
<Keyboard x:FactoryMethod="Create">
|
||||
<x:Arguments>
|
||||
<KeyboardFlags>None</KeyboardFlags>
|
||||
</x:Arguments>
|
||||
</Keyboard>
|
||||
</Entry.Keyboard>
|
||||
</controls:MonoEntry>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowHiddenValue, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleHiddenValueCommand}"
|
||||
IsVisible="{Binding ShowViewHidden}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyFieldCommand}"
|
||||
IsVisible="{Binding ShowCopyButton}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Copy}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class HiddenCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public HiddenCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.LinkedCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:LinkedCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
<StackLayout
|
||||
StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding IsEditing}">
|
||||
<Picker
|
||||
x:Name="_linkedFieldOptionPicker"
|
||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||
ItemDisplayBinding="{Binding Key}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class LinkedCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public LinkedCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.TextCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:TextCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<Label
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
<Entry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyFieldCommand}"
|
||||
IsVisible="{Binding ShowCopyButton}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Copy}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
@ -0,0 +1,12 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class TextCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public TextCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public abstract class BaseCustomFieldItemViewModel : ExtendedViewModel, ICustomFieldItemViewModel
|
||||
{
|
||||
protected FieldView _field;
|
||||
protected bool _isEditing;
|
||||
private string[] _additionalFieldProperties = new string[]
|
||||
{
|
||||
nameof(ValueText),
|
||||
nameof(ShowCopyButton)
|
||||
};
|
||||
|
||||
public BaseCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
||||
{
|
||||
_field = field;
|
||||
_isEditing = isEditing;
|
||||
FieldOptionsCommand = new Command(() => fieldOptionsCommand?.Execute(this));
|
||||
}
|
||||
|
||||
public FieldView Field
|
||||
{
|
||||
get => _field;
|
||||
set => SetProperty(ref _field, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ValueText),
|
||||
nameof(ShowCopyButton),
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsEditing => _isEditing;
|
||||
|
||||
public virtual bool ShowCopyButton => false;
|
||||
|
||||
public virtual string ValueText => _field.Value;
|
||||
|
||||
public ICommand FieldOptionsCommand { get; }
|
||||
|
||||
public void TriggerFieldChanged()
|
||||
{
|
||||
TriggerPropertyChanged(nameof(Field), _additionalFieldProperties);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class BooleanCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
public BooleanCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
}
|
||||
|
||||
public bool BooleanValue
|
||||
{
|
||||
get => bool.TryParse(Field.Value, out var boolVal) && boolVal;
|
||||
set
|
||||
{
|
||||
Field.Value = value.ToString().ToLower();
|
||||
TriggerPropertyChanged(nameof(BooleanValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public interface ICustomFieldItemFactory
|
||||
{
|
||||
ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
||||
bool isEditing,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
ICommand copyFieldCommand,
|
||||
ICommand fieldOptionsCommand);
|
||||
}
|
||||
|
||||
public class CustomFieldItemFactory : ICustomFieldItemFactory
|
||||
{
|
||||
readonly II18nService _i18nService;
|
||||
readonly IEventService _eventService;
|
||||
|
||||
public CustomFieldItemFactory(II18nService i18nService, IEventService eventService)
|
||||
{
|
||||
_i18nService = i18nService;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
||||
bool isEditing,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
ICommand copyFieldCommand,
|
||||
ICommand fieldOptionsCommand)
|
||||
{
|
||||
switch (field.Type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
return new TextCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, copyFieldCommand);
|
||||
case FieldType.Boolean:
|
||||
return new BooleanCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand);
|
||||
case FieldType.Hidden:
|
||||
return new HiddenCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, passwordPromptable, _eventService, copyFieldCommand);
|
||||
case FieldType.Linked:
|
||||
return new LinkedCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, _i18nService);
|
||||
default:
|
||||
throw new NotImplementedException("There is no custom field item for field type " + field.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class HiddenCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
private readonly CipherView _cipher;
|
||||
private readonly IPasswordPromptable _passwordPromptable;
|
||||
private readonly IEventService _eventService;
|
||||
private bool _showHiddenValue;
|
||||
|
||||
public HiddenCustomFieldItemViewModel(FieldView field,
|
||||
bool isEditing,
|
||||
ICommand fieldOptionsCommand,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
IEventService eventService,
|
||||
ICommand copyFieldCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
_cipher = cipher;
|
||||
_passwordPromptable = passwordPromptable;
|
||||
_eventService = eventService;
|
||||
|
||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
||||
ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, (Func<bool>)null, ex =>
|
||||
{
|
||||
#if !FDROID
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
public ICommand CopyFieldCommand { get; }
|
||||
|
||||
public ICommand ToggleHiddenValueCommand { get; set; }
|
||||
|
||||
public bool ShowHiddenValue
|
||||
{
|
||||
get => _showHiddenValue;
|
||||
set => SetProperty(ref _showHiddenValue, value);
|
||||
}
|
||||
|
||||
public bool ShowViewHidden => _cipher.ViewPassword || (_isEditing && _field.NewField);
|
||||
|
||||
public override bool ShowCopyButton => !_isEditing && _cipher.ViewPassword && !string.IsNullOrWhiteSpace(Field.Value);
|
||||
|
||||
public async Task ToggleHiddenValueAsync()
|
||||
{
|
||||
if (!_isEditing && !await _passwordPromptable.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ShowHiddenValue = !ShowHiddenValue;
|
||||
if (ShowHiddenValue && (!_isEditing || _cipher?.Id != null))
|
||||
{
|
||||
await _eventService.CollectAsync(
|
||||
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public interface ICustomFieldItemViewModel
|
||||
{
|
||||
FieldView Field { get; set; }
|
||||
|
||||
bool ShowCopyButton { get; }
|
||||
|
||||
void TriggerFieldChanged();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class LinkedCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
private readonly CipherView _cipher;
|
||||
private readonly II18nService _i18nService;
|
||||
private int _linkedFieldOptionSelectedIndex;
|
||||
|
||||
public LinkedCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, CipherView cipher, II18nService i18nService)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
_cipher = cipher;
|
||||
_i18nService = i18nService;
|
||||
|
||||
LinkedFieldOptionSelectedIndex = Field.LinkedId.HasValue
|
||||
? LinkedFieldOptions.FindIndex(lfo => lfo.Value == Field.LinkedId.Value)
|
||||
: 0;
|
||||
|
||||
if (isEditing && Field.LinkedId is null)
|
||||
{
|
||||
field.LinkedId = LinkedFieldOptions[0].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ValueText
|
||||
{
|
||||
get
|
||||
{
|
||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||
return $"{BitwardenIcons.Link} {_i18nService.T(i18nKey)}";
|
||||
}
|
||||
}
|
||||
|
||||
public int LinkedFieldOptionSelectedIndex
|
||||
{
|
||||
get => _linkedFieldOptionSelectedIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _linkedFieldOptionSelectedIndex, value))
|
||||
{
|
||||
LinkedFieldValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions
|
||||
{
|
||||
get => _cipher.LinkedFieldOptions
|
||||
.Select(kvp => new KeyValuePair<string, LinkedIdType>(_i18nService.T(kvp.Key), kvp.Value))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void LinkedFieldValueChanged()
|
||||
{
|
||||
if (Field != null && LinkedFieldOptionSelectedIndex > -1)
|
||||
{
|
||||
Field.LinkedId = LinkedFieldOptions.Find(lfo =>
|
||||
lfo.Value == LinkedFieldOptions[LinkedFieldOptionSelectedIndex].Value).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class TextCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
public TextCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, ICommand copyFieldCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
||||
}
|
||||
|
||||
public override bool ShowCopyButton => !_isEditing && !string.IsNullOrWhiteSpace(Field.Value);
|
||||
|
||||
public ICommand CopyFieldCommand { get; }
|
||||
}
|
||||
}
|
@ -73,4 +73,3 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
||||
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
|
||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||
xmlns:dts="clr-namespace:Bit.App.Lists.DataTemplateSelectors"
|
||||
xmlns:il="clr-namespace:Bit.App.Lists.ItemLayouts.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="pages:CipherAddEditPageViewModel"
|
||||
x:Name="_page"
|
||||
@ -53,6 +55,25 @@
|
||||
IsDestructive="True"
|
||||
x:Name="_deleteItem"
|
||||
x:Key="deleteItem" />
|
||||
|
||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
||||
<il:TextCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
||||
<il:BooleanCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
||||
<il:HiddenCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
||||
<il:LinkedCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
|
||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
||||
TextTemplate="{StaticResource TextCustomFieldDataTemplate}"
|
||||
BooleanTemplate="{StaticResource BooleanCustomFieldDataTemplate}"
|
||||
HiddenTemplate="{StaticResource HiddenCustomFieldDataTemplate}"
|
||||
LinkedTemplate="{StaticResource LinkedCustomFieldDataTemplate}"/>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
@ -647,101 +668,10 @@
|
||||
<Label Text="{u:I18n CustomFields, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<controls:RepeaterView ItemsSource="{Binding Fields}">
|
||||
<controls:RepeaterView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:CipherAddEditPageFieldViewModel">
|
||||
<StackLayout Spacing="0" Padding="0">
|
||||
<Grid StyleClass="box-row, box-row-input">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
IsVisible="{Binding IsBooleanType, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
IsVisible="{Binding IsBooleanType, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
VerticalOptions="FillAndExpand"
|
||||
VerticalTextAlignment="Center"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2" />
|
||||
<Entry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsTextType}" />
|
||||
<controls:MonoEntry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsHiddenType}"
|
||||
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
|
||||
IsEnabled="{Binding ShowViewHidden}"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False">
|
||||
<Entry.Keyboard>
|
||||
<Keyboard x:FactoryMethod="Create">
|
||||
<x:Arguments>
|
||||
<KeyboardFlags>None</KeyboardFlags>
|
||||
</x:Arguments>
|
||||
</Keyboard>
|
||||
</Entry.Keyboard>
|
||||
</controls:MonoEntry>
|
||||
<StackLayout
|
||||
StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding IsLinkedType}">
|
||||
<Picker
|
||||
x:Name="_linkedFieldOptionPicker"
|
||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||
ItemDisplayBinding="{Binding Key}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
<Switch
|
||||
IsToggled="{Binding BooleanValue}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding IsBooleanType}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowHiddenValueIcon}"
|
||||
Command="{Binding ToggleHiddenValueCommand}"
|
||||
IsVisible="{Binding ShowViewHidden}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding BindingContext.FieldOptionsCommand, Source={x:Reference _page}}"
|
||||
CommandParameter="{Binding .}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsBooleanType}" />
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</controls:RepeaterView.ItemTemplate>
|
||||
</controls:RepeaterView>
|
||||
<StackLayout
|
||||
Spacing="0"
|
||||
BindableLayout.ItemsSource="{Binding Fields}"
|
||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
||||
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
|
||||
Clicked="NewField_Clicked"></Button>
|
||||
</StackLayout>
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
@ -25,6 +26,7 @@ namespace Bit.App.Pages
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ICustomFieldItemFactory _customFieldItemFactory;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
|
||||
private bool _showNotesSeparator;
|
||||
@ -74,6 +76,7 @@ namespace Bit.App.Pages
|
||||
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
|
||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
|
||||
GeneratePasswordCommand = new Command(GeneratePassword);
|
||||
@ -81,12 +84,12 @@ namespace Bit.App.Pages
|
||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||
ToggleCardCodeCommand = new Command(ToggleCardCode);
|
||||
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
|
||||
FieldOptionsCommand = new Command<CipherAddEditPageFieldViewModel>(FieldOptions);
|
||||
FieldOptionsCommand = new Command<ICustomFieldItemViewModel>(FieldOptions);
|
||||
PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
|
||||
CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||
GenerateUsernameCommand = new AsyncCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false);
|
||||
Uris = new ExtendedObservableCollection<LoginUriView>();
|
||||
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
|
||||
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
|
||||
Collections = new ExtendedObservableCollection<CollectionViewModel>();
|
||||
AllowPersonal = true;
|
||||
|
||||
@ -161,7 +164,7 @@ namespace Bit.App.Pages
|
||||
public List<KeyValuePair<string, string>> FolderOptions { get; set; }
|
||||
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
|
||||
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
|
||||
public ExtendedObservableCollection<CipherAddEditPageFieldViewModel> Fields { get; set; }
|
||||
public ExtendedObservableCollection<ICustomFieldItemViewModel> Fields { get; set; }
|
||||
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
|
||||
|
||||
public int TypeSelectedIndex
|
||||
@ -414,7 +417,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (Cipher.Fields != null)
|
||||
{
|
||||
Fields.ResetWithRange(Cipher.Fields?.Select(f => new CipherAddEditPageFieldViewModel(Cipher, f)));
|
||||
Fields.ResetWithRange(Cipher.Fields?.Select(f => _customFieldItemFactory.CreateCustomFieldItem(f, true, Cipher, null, null, FieldOptionsCommand)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -656,7 +659,7 @@ namespace Bit.App.Pages
|
||||
Uris.Add(new LoginUriView());
|
||||
}
|
||||
|
||||
public async void FieldOptions(CipherAddEditPageFieldViewModel field)
|
||||
public async void FieldOptions(ICustomFieldItemViewModel field)
|
||||
{
|
||||
if (!(Page as CipherAddEditPage).DoOnce())
|
||||
{
|
||||
@ -718,15 +721,15 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (Fields == null)
|
||||
{
|
||||
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
|
||||
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
|
||||
}
|
||||
var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
|
||||
Fields.Add(new CipherAddEditPageFieldViewModel(Cipher, new FieldView
|
||||
Fields.Add(_customFieldItemFactory.CreateCustomFieldItem(new FieldView
|
||||
{
|
||||
Type = type,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
NewField = true,
|
||||
}));
|
||||
}, true, Cipher, null, null, FieldOptionsCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@ -788,7 +791,7 @@ namespace Bit.App.Pages
|
||||
TriggerCipherChanged();
|
||||
|
||||
// Linked Custom Fields only apply to a specific item type
|
||||
foreach (var field in Fields.Where(f => f.IsLinkedType).ToList())
|
||||
foreach (var field in Fields.OfType<LinkedCustomFieldItemViewModel>().ToList())
|
||||
{
|
||||
Fields.Remove(field);
|
||||
}
|
||||
@ -871,113 +874,4 @@ namespace Bit.App.Pages
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
public class CipherAddEditPageFieldViewModel : ExtendedViewModel
|
||||
{
|
||||
private II18nService _i18nService;
|
||||
private FieldView _field;
|
||||
private CipherView _cipher;
|
||||
private bool _showHiddenValue;
|
||||
private bool _booleanValue;
|
||||
private int _linkedFieldOptionSelectedIndex;
|
||||
private string[] _additionalFieldProperties = new string[]
|
||||
{
|
||||
nameof(IsBooleanType),
|
||||
nameof(IsHiddenType),
|
||||
nameof(IsTextType),
|
||||
nameof(IsLinkedType),
|
||||
};
|
||||
|
||||
public CipherAddEditPageFieldViewModel(CipherView cipher, FieldView field)
|
||||
{
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_cipher = cipher;
|
||||
Field = field;
|
||||
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
|
||||
BooleanValue = IsBooleanType && field.Value == "true";
|
||||
LinkedFieldOptionSelectedIndex = !Field.LinkedId.HasValue ? 0 :
|
||||
LinkedFieldOptions.FindIndex(lfo => lfo.Value == Field.LinkedId.Value);
|
||||
}
|
||||
|
||||
public FieldView Field
|
||||
{
|
||||
get => _field;
|
||||
set => SetProperty(ref _field, value, additionalPropertyNames: _additionalFieldProperties);
|
||||
}
|
||||
|
||||
public bool ShowHiddenValue
|
||||
{
|
||||
get => _showHiddenValue;
|
||||
set => SetProperty(ref _showHiddenValue, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowHiddenValueIcon)
|
||||
});
|
||||
}
|
||||
|
||||
public bool BooleanValue
|
||||
{
|
||||
get => _booleanValue;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _booleanValue, value);
|
||||
if (IsBooleanType)
|
||||
{
|
||||
Field.Value = value ? "true" : "false";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int LinkedFieldOptionSelectedIndex
|
||||
{
|
||||
get => _linkedFieldOptionSelectedIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _linkedFieldOptionSelectedIndex, value))
|
||||
{
|
||||
LinkedFieldValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions
|
||||
{
|
||||
get => _cipher.LinkedFieldOptions?
|
||||
.Select(kvp => new KeyValuePair<string, LinkedIdType>(_i18nService.T(kvp.Key), kvp.Value))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Command ToggleHiddenValueCommand { get; set; }
|
||||
|
||||
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public bool IsTextType => _field.Type == FieldType.Text;
|
||||
public bool IsBooleanType => _field.Type == FieldType.Boolean;
|
||||
public bool IsHiddenType => _field.Type == FieldType.Hidden;
|
||||
public bool IsLinkedType => _field.Type == FieldType.Linked;
|
||||
public bool ShowViewHidden => IsHiddenType && (_cipher.ViewPassword || _field.NewField);
|
||||
|
||||
public void ToggleHiddenValue()
|
||||
{
|
||||
ShowHiddenValue = !ShowHiddenValue;
|
||||
if (ShowHiddenValue && _cipher?.Id != null)
|
||||
{
|
||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
var task = eventService.CollectAsync(EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerFieldChanged()
|
||||
{
|
||||
TriggerPropertyChanged(nameof(Field), _additionalFieldProperties);
|
||||
}
|
||||
|
||||
private void LinkedFieldValueChanged()
|
||||
{
|
||||
if (Field != null && LinkedFieldOptionSelectedIndex > -1)
|
||||
{
|
||||
Field.LinkedId = LinkedFieldOptions.Find(lfo =>
|
||||
lfo.Value == LinkedFieldOptions[LinkedFieldOptionSelectedIndex].Value).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
||||
xmlns:dts="clr-namespace:Bit.App.Lists.DataTemplateSelectors"
|
||||
xmlns:il="clr-namespace:Bit.App.Lists.ItemLayouts.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="pages:CipherDetailsPageViewModel"
|
||||
x:Name="_page"
|
||||
@ -46,6 +48,25 @@
|
||||
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary"
|
||||
x:Name="_cloneItem" x:Key="cloneItem" />
|
||||
|
||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
||||
<il:TextCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
||||
<il:BooleanCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
||||
<il:HiddenCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
||||
<il:LinkedCustomFieldItemLayout />
|
||||
</DataTemplate>
|
||||
|
||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
||||
TextTemplate="{StaticResource TextCustomFieldDataTemplate}"
|
||||
BooleanTemplate="{StaticResource BooleanCustomFieldDataTemplate}"
|
||||
HiddenTemplate="{StaticResource HiddenCustomFieldDataTemplate}"
|
||||
LinkedTemplate="{StaticResource LinkedCustomFieldDataTemplate}"/>
|
||||
|
||||
<ScrollView x:Key="scrollView" x:Name="_scrollView">
|
||||
<StackLayout Spacing="20" x:Name="_mainLayout">
|
||||
<StackLayout StyleClass="box">
|
||||
@ -559,85 +580,10 @@
|
||||
<Label Text="{u:I18n CustomFields, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<controls:RepeaterView ItemsSource="{Binding Fields}">
|
||||
<controls:RepeaterView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:CipherDetailsPageFieldViewModel">
|
||||
<StackLayout Spacing="0" Padding="0">
|
||||
<Grid StyleClass="box-row">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<Label
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsTextType}" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsLinkedType}" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
AutomationProperties.IsInAccessibleTree="true"
|
||||
AutomationProperties.Name="{Binding ValueAccessibilityText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsBooleanType}"
|
||||
Margin="0, 5, 0, 0" />
|
||||
<StackLayout IsVisible="{Binding IsHiddenType}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0">
|
||||
<controls:MonoLabel
|
||||
Text="{Binding ColoredHiddenValue, Mode=OneWay}"
|
||||
StyleClass="box-value, text-html"
|
||||
IsVisible="{Binding ShowHiddenValue}" />
|
||||
<controls:MonoLabel
|
||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowHiddenValueIcon}"
|
||||
Command="{Binding ToggleHiddenValueCommand}"
|
||||
IsVisible="{Binding ShowViewHidden}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding BindingContext.CopyFieldCommand, Source={x:Reference _page}}"
|
||||
CommandParameter="{Binding Field}"
|
||||
IsVisible="{Binding ShowCopyButton}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Copy}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</controls:RepeaterView.ItemTemplate>
|
||||
</controls:RepeaterView>
|
||||
<StackLayout
|
||||
Spacing="0"
|
||||
BindableLayout.ItemsSource="{Binding Fields}"
|
||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAttachments}">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
|
@ -5,6 +5,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
@ -18,7 +19,7 @@ using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class CipherDetailsPageViewModel : BaseCipherViewModel
|
||||
public class CipherDetailsPageViewModel : BaseCipherViewModel, IPasswordPromptable
|
||||
{
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IStateService _stateService;
|
||||
@ -28,9 +29,10 @@ namespace Bit.App.Pages
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly ICustomFieldItemFactory _customFieldItemFactory;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
|
||||
private List<CipherDetailsPageFieldViewModel> _fields;
|
||||
private List<ICustomFieldItemViewModel> _fields;
|
||||
private bool _canAccessPremium;
|
||||
private bool _showPassword;
|
||||
private bool _showCardNumber;
|
||||
@ -58,6 +60,7 @@ namespace Bit.App.Pages
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
|
||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
|
||||
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||
@ -99,7 +102,7 @@ namespace Bit.App.Pages
|
||||
nameof(CanEdit),
|
||||
nameof(ShowUpgradePremiumTotpText)
|
||||
};
|
||||
public List<CipherDetailsPageFieldViewModel> Fields
|
||||
public List<ICustomFieldItemViewModel> Fields
|
||||
{
|
||||
get => _fields;
|
||||
set => SetProperty(ref _fields, value);
|
||||
@ -252,7 +255,10 @@ namespace Bit.App.Pages
|
||||
}
|
||||
Cipher = await cipher.DecryptAsync();
|
||||
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
Fields = Cipher.Fields?.Select(f => new CipherDetailsPageFieldViewModel(this, Cipher, f)).ToList();
|
||||
|
||||
Fields = Cipher.Fields?
|
||||
.Select(f => _customFieldItemFactory.CreateCustomFieldItem(f, false, Cipher, this, CopyFieldCommand, null))
|
||||
.ToList();
|
||||
|
||||
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
||||
(Cipher.OrganizationUseTotp || CanAccessPremium))
|
||||
@ -665,7 +671,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> PromptPasswordAsync()
|
||||
public async Task<bool> PromptPasswordAsync()
|
||||
{
|
||||
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
|
||||
{
|
||||
@ -675,110 +681,4 @@ namespace Bit.App.Pages
|
||||
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class CipherDetailsPageFieldViewModel : ExtendedViewModel
|
||||
{
|
||||
private II18nService _i18nService;
|
||||
private CipherDetailsPageViewModel _vm;
|
||||
private FieldView _field;
|
||||
private CipherView _cipher;
|
||||
private bool _showHiddenValue;
|
||||
|
||||
public CipherDetailsPageFieldViewModel(CipherDetailsPageViewModel vm, CipherView cipher, FieldView field)
|
||||
{
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_vm = vm;
|
||||
_cipher = cipher;
|
||||
Field = field;
|
||||
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
|
||||
}
|
||||
|
||||
public FieldView Field
|
||||
{
|
||||
get => _field;
|
||||
set => SetProperty(ref _field, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ValueText),
|
||||
nameof(ValueAccessibilityText),
|
||||
nameof(IsBooleanType),
|
||||
nameof(IsHiddenType),
|
||||
nameof(IsTextType),
|
||||
nameof(ShowCopyButton),
|
||||
});
|
||||
}
|
||||
|
||||
public bool ShowHiddenValue
|
||||
{
|
||||
get => _showHiddenValue;
|
||||
set => SetProperty(ref _showHiddenValue, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowHiddenValueIcon)
|
||||
});
|
||||
}
|
||||
|
||||
public string ValueText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsBooleanType)
|
||||
{
|
||||
return _field.BoolValue ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
||||
}
|
||||
else if (IsLinkedType)
|
||||
{
|
||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||
return BitwardenIcons.Link + _i18nService.T(i18nKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _field.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ValueAccessibilityText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsBooleanType)
|
||||
{
|
||||
return _field.BoolValue ? AppResources.Enabled : AppResources.Disabled;
|
||||
}
|
||||
|
||||
return ValueText;
|
||||
}
|
||||
}
|
||||
|
||||
public FormattedString ColoredHiddenValue => GeneratedValueFormatter.Format(_field.Value);
|
||||
|
||||
public Command ToggleHiddenValueCommand { get; set; }
|
||||
|
||||
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public bool IsTextType => _field.Type == Core.Enums.FieldType.Text;
|
||||
public bool IsBooleanType => _field.Type == Core.Enums.FieldType.Boolean;
|
||||
public bool IsHiddenType => _field.Type == Core.Enums.FieldType.Hidden;
|
||||
public bool IsLinkedType => _field.Type == Core.Enums.FieldType.Linked;
|
||||
public bool ShowViewHidden => IsHiddenType && _cipher.ViewPassword;
|
||||
public bool ShowCopyButton => _field.Type != Core.Enums.FieldType.Boolean &&
|
||||
!string.IsNullOrWhiteSpace(_field.Value) &&
|
||||
!(IsHiddenType && !_cipher.ViewPassword) &&
|
||||
_field.Type != FieldType.Linked;
|
||||
|
||||
public async void ToggleHiddenValue()
|
||||
{
|
||||
if (!await _vm.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ShowHiddenValue = !ShowHiddenValue;
|
||||
if (ShowHiddenValue)
|
||||
{
|
||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
var task = eventService.CollectAsync(
|
||||
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
src/App/Utilities/AppSetup.cs
Normal file
23
src/App/Utilities/AppSetup.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public interface IAppSetup
|
||||
{
|
||||
void InitializeServicesLastChance();
|
||||
}
|
||||
|
||||
public class AppSetup : IAppSetup
|
||||
{
|
||||
public void InitializeServicesLastChance()
|
||||
{
|
||||
var i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
|
||||
// TODO: This could be further improved by Lazy Registration since it may not be needed at all
|
||||
ServiceContainer.Register<ICustomFieldItemFactory>("customFieldItemFactory", new CustomFieldItemFactory(i18nService, eventService));
|
||||
}
|
||||
}
|
||||
}
|
24
src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs
Normal file
24
src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public class BooleanToBoxRowInputPaddingConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter,
|
||||
System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if (targetType == typeof(Thickness))
|
||||
{
|
||||
return ((bool?)value).GetValueOrDefault() ? new Thickness(0, 10, 0, 0) : new Thickness(0, 10);
|
||||
}
|
||||
throw new InvalidOperationException("The target must be a thickness.");
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter,
|
||||
System.Globalization.CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
9
src/App/Utilities/IPasswordPromptable.cs
Normal file
9
src/App/Utilities/IPasswordPromptable.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public interface IPasswordPromptable
|
||||
{
|
||||
Task<bool> PromptPasswordAsync();
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@ -11,54 +9,24 @@ namespace Bit.App.Utilities
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var cipher = value as CipherView;
|
||||
return GetIcon(cipher);
|
||||
if (value is CipherView cipher)
|
||||
{
|
||||
return cipher.GetIcon();
|
||||
}
|
||||
|
||||
if (value is bool boolVal
|
||||
&&
|
||||
parameter is BooleanGlyphType boolGlyphType)
|
||||
{
|
||||
return IconGlyphExtensions.GetBooleanIconGlyph(boolVal, boolGlyphType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private string GetIcon(CipherView cipher)
|
||||
{
|
||||
string icon = null;
|
||||
switch (cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
icon = GetLoginIconGlyph(cipher);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
icon = BitwardenIcons.StickyNote;
|
||||
break;
|
||||
case CipherType.Card:
|
||||
icon = BitwardenIcons.CreditCard;
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
icon = BitwardenIcons.IdCard;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
string GetLoginIconGlyph(CipherView cipher)
|
||||
{
|
||||
var icon = BitwardenIcons.Globe;
|
||||
if (cipher.Login.Uri != null)
|
||||
{
|
||||
var hostnameUri = cipher.Login.Uri;
|
||||
if (hostnameUri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
icon = BitwardenIcons.Android;
|
||||
}
|
||||
else if (hostnameUri.StartsWith(Constants.iOSAppProtocol))
|
||||
{
|
||||
icon = BitwardenIcons.Apple;
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
69
src/App/Utilities/IconGlyphExtensions.cs
Normal file
69
src/App/Utilities/IconGlyphExtensions.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public static class IconGlyphExtensions
|
||||
{
|
||||
public static string GetIcon(this CipherView cipher)
|
||||
{
|
||||
string icon = null;
|
||||
switch (cipher.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
icon = GetLoginIconGlyph(cipher);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
icon = BitwardenIcons.StickyNote;
|
||||
break;
|
||||
case CipherType.Card:
|
||||
icon = BitwardenIcons.CreditCard;
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
icon = BitwardenIcons.IdCard;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
static string GetLoginIconGlyph(CipherView cipher)
|
||||
{
|
||||
var icon = BitwardenIcons.Globe;
|
||||
if (cipher.Login.Uri != null)
|
||||
{
|
||||
var hostnameUri = cipher.Login.Uri;
|
||||
if (hostnameUri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
icon = BitwardenIcons.Android;
|
||||
}
|
||||
else if (hostnameUri.StartsWith(Constants.iOSAppProtocol))
|
||||
{
|
||||
icon = BitwardenIcons.Apple;
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
public static string GetBooleanIconGlyph(bool value, BooleanGlyphType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BooleanGlyphType.Checkbox:
|
||||
return value ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
||||
case BooleanGlyphType.Eye:
|
||||
return value ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum BooleanGlyphType
|
||||
{
|
||||
Checkbox,
|
||||
Eye
|
||||
}
|
||||
}
|
@ -199,6 +199,7 @@ namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||
|
||||
InitializeAppSetup();
|
||||
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||
ServiceContainer.Resolve<IApiService>("apiService"),
|
||||
@ -229,5 +230,12 @@ namespace Bit.iOS.Core.Utilities
|
||||
await postBootstrapFunc.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeAppSetup()
|
||||
{
|
||||
var appSetup = new AppSetup();
|
||||
appSetup.InitializeServicesLastChance();
|
||||
ServiceContainer.Register<IAppSetup>("appSetup", appSetup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user