mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
10 Commits
PM-4047/fi
...
beeep-totp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a484ca633c | ||
|
|
655b51b6a5 | ||
|
|
0b626cedc7 | ||
|
|
3ac2580742 | ||
|
|
008ed8eb56 | ||
|
|
26e0e43bb4 | ||
|
|
0ad992faec | ||
|
|
98dd8298ea | ||
|
|
bb37bac620 | ||
|
|
b02c58e362 |
@@ -127,6 +127,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
|
<Folder Include="Pages\Authenticator\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Bit.App.Controls.AuthenticatorViewCell"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
|
||||||
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
|
StyleClass="list-row, list-row-platform"
|
||||||
|
RowSpacing="0"
|
||||||
|
ColumnSpacing="0"
|
||||||
|
x:DataType="pages:AuthenticatorPageListItem">
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||||
|
<u:IconImageConverter x:Key="iconImageConverter"/>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="40" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="60" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<controls:IconLabel
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
StyleClass="list-icon, list-icon-platform"
|
||||||
|
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||||
|
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
|
|
||||||
|
<ff:CachedImage
|
||||||
|
Grid.Column="0"
|
||||||
|
BitmapOptimizations="True"
|
||||||
|
ErrorPlaceholder="login.png"
|
||||||
|
LoadingPlaceholder="login.png"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
WidthRequest="22"
|
||||||
|
HeightRequest="22"
|
||||||
|
IsVisible="{Binding ShowIconImage}"
|
||||||
|
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
|
|
||||||
|
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<controls:MonoLabel
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
StyleClass="list-title, list-title-platform"
|
||||||
|
Text="{Binding TotpCodeFormatted, Mode=OneWay}" />
|
||||||
|
<Label
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
|
Text="{Binding Cipher.Name}" />
|
||||||
|
<controls:IconLabel
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalOptions="Start"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
StyleClass="list-title-icon"
|
||||||
|
Margin="5, 0, 0, 0"
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
||||||
|
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Shared}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Label
|
||||||
|
Text="{Binding TotpSec, Mode=OneWay}"
|
||||||
|
Style="{DynamicResource textTotp}"
|
||||||
|
Margin="0, 0, 10, 0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
HorizontalTextAlignment="End"
|
||||||
|
VerticalOptions="CenterAndExpand" />
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
|
Command="{Binding CopyCommand}"
|
||||||
|
CommandParameter="LoginTotp"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Padding="0,0,1,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
||||||
|
</controls:ExtendedGrid>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public partial class AuthenticatorViewCell : ExtendedGrid
|
||||||
|
{
|
||||||
|
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
|
||||||
|
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
|
||||||
|
|
||||||
|
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
|
||||||
|
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
|
||||||
|
|
||||||
|
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
|
||||||
|
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
|
||||||
|
|
||||||
|
//public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||||
|
// nameof(ButtonCommand), typeof(Command<CipherView>), typeof(AuthenticatorViewCell));
|
||||||
|
|
||||||
|
public AuthenticatorViewCell()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command CopyCommand { get; set; }
|
||||||
|
|
||||||
|
public CipherView Cipher
|
||||||
|
{
|
||||||
|
get => GetValue(CipherProperty) as CipherView;
|
||||||
|
set => SetValue(CipherProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool? WebsiteIconsEnabled
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(WebsiteIconsEnabledProperty);
|
||||||
|
set => SetValue(WebsiteIconsEnabledProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long TotpSec
|
||||||
|
{
|
||||||
|
get => (long)GetValue(TotpSecProperty);
|
||||||
|
set => SetValue(TotpSecProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowIconImage
|
||||||
|
{
|
||||||
|
get => WebsiteIconsEnabled ?? false
|
||||||
|
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||||
|
&& IconImageSource != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _iconImageSource = string.Empty;
|
||||||
|
public string IconImageSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||||
|
{
|
||||||
|
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||||
|
}
|
||||||
|
return _iconImageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _totpCodeFormatted = "938 928";
|
||||||
|
public string TotpCodeFormatted
|
||||||
|
{
|
||||||
|
get => _totpCodeFormatted;
|
||||||
|
set => _totpCodeFormatted = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public Command<CipherView> ButtonCommand
|
||||||
|
//{
|
||||||
|
// get => GetValue(ButtonCommandProperty) as Command<CipherView>;
|
||||||
|
// set => SetValue(ButtonCommandProperty, value);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//protected override void OnPropertyChanged(string propertyName = null)
|
||||||
|
//{
|
||||||
|
// base.OnPropertyChanged(propertyName);
|
||||||
|
// if (propertyName == CipherProperty.PropertyName)
|
||||||
|
// {
|
||||||
|
// if (Cipher == null)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// _cipherLabel.Text = Cipher.Name;
|
||||||
|
// }
|
||||||
|
// else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
|
||||||
|
// {
|
||||||
|
// if (Cipher == null)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// ((AuthenticatorViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
|
||||||
|
// }
|
||||||
|
// else if (propertyName == TotpSecProperty.PropertyName)
|
||||||
|
// {
|
||||||
|
// if (Cipher == null)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// ((AuthenticatorViewCellViewModel)BindingContext).UpdateTotpSec(TotpSec);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
private void MoreButton_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cipher = ((sender as MiButton)?.BindingContext as AuthenticatorViewCellViewModel)?.Cipher;
|
||||||
|
if (cipher != null)
|
||||||
|
{
|
||||||
|
//ButtonCommand?.Execute(cipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public class AuthenticatorViewCellViewModel : ExtendedViewModel
|
||||||
|
{
|
||||||
|
private CipherView _cipher;
|
||||||
|
private string _totpCodeFormatted = "938 928";
|
||||||
|
private string _totpSec;
|
||||||
|
private bool _websiteIconsEnabled;
|
||||||
|
private string _iconImageSource = string.Empty;
|
||||||
|
|
||||||
|
public AuthenticatorViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
|
||||||
|
{
|
||||||
|
Cipher = cipherView;
|
||||||
|
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command CopyCommand { get; set; }
|
||||||
|
|
||||||
|
public CipherView Cipher
|
||||||
|
{
|
||||||
|
get => _cipher;
|
||||||
|
set => SetProperty(ref _cipher, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TotpCodeFormatted
|
||||||
|
{
|
||||||
|
get => _totpCodeFormatted;
|
||||||
|
set => SetProperty(ref _totpCodeFormatted, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TotpSec
|
||||||
|
{
|
||||||
|
get => _totpSec;
|
||||||
|
set => SetProperty(ref _totpSec, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WebsiteIconsEnabled
|
||||||
|
{
|
||||||
|
get => _websiteIconsEnabled;
|
||||||
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowIconImage
|
||||||
|
{
|
||||||
|
get => WebsiteIconsEnabled
|
||||||
|
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||||
|
&& IconImageSource != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string IconImageSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||||
|
{
|
||||||
|
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||||
|
}
|
||||||
|
return _iconImageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTotpSec(long totpSec)
|
||||||
|
{
|
||||||
|
_totpSec = totpSec.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
//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;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
97
src/App/Pages/Authenticator/AuthenticatorPage.xaml
Normal file
97
src/App/Pages/Authenticator/AuthenticatorPage.xaml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<pages:BaseContentPage
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Bit.App.Pages.AuthenticatorPage"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
x:DataType="pages:AuthenticatorPageViewModel"
|
||||||
|
Title="{Binding PageTitle}"
|
||||||
|
x:Name="_page">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:AuthenticatorPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Search}" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
|
||||||
|
<ToolbarItem x:Name="_aboutIconItem" x:Key="aboutIconItem" Icon="info.png"
|
||||||
|
Clicked="About_Clicked" Order="Primary" Priority="-1"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n AboutSend}" />
|
||||||
|
<ToolbarItem x:Name="_syncItem" x:Key="syncItem" Text="{u:I18n Sync}"
|
||||||
|
Clicked="Sync_Clicked" Order="Secondary" />
|
||||||
|
<ToolbarItem x:Name="_lockItem" x:Key="lockItem" Text="{u:I18n Lock}"
|
||||||
|
Clicked="Lock_Clicked" Order="Secondary" />
|
||||||
|
<ToolbarItem x:Name="_aboutTextItem" x:Key="aboutTextItem" Text="{u:I18n AboutSend}"
|
||||||
|
Clicked="About_Clicked" Order="Secondary" />
|
||||||
|
<ToolbarItem x:Name="_addItem" x:Key="addItem" Icon="plus.png"
|
||||||
|
Clicked="AddButton_Clicked" Order="Primary"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n AddItem}" />
|
||||||
|
|
||||||
|
<DataTemplate x:Key="authenticatorTemplate"
|
||||||
|
x:DataType="pages:AuthenticatorPageListItem">
|
||||||
|
<controls:AuthenticatorViewCell
|
||||||
|
Cipher="{Binding Cipher}"
|
||||||
|
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
|
||||||
|
TotpSec="{Binding TotpSec}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
|
<RefreshView>
|
||||||
|
<controls:ExtendedCollectionView
|
||||||
|
ItemsSource="{Binding Items}"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionChanged="RowSelected"
|
||||||
|
StyleClass="list, list-platform">
|
||||||
|
|
||||||
|
<controls:ExtendedCollectionView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="pages:AuthenticatorPageListItem">
|
||||||
|
<controls:AuthenticatorViewCell />
|
||||||
|
</DataTemplate>
|
||||||
|
</controls:ExtendedCollectionView.ItemTemplate>
|
||||||
|
|
||||||
|
</controls:ExtendedCollectionView>
|
||||||
|
</RefreshView>
|
||||||
|
|
||||||
|
</StackLayout>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
|
||||||
|
<AbsoluteLayout
|
||||||
|
x:Name="_absLayout"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
HorizontalOptions="FillAndExpand">
|
||||||
|
<ContentView
|
||||||
|
x:Name="_mainContent"
|
||||||
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1" />
|
||||||
|
<Button
|
||||||
|
x:Name="_fab"
|
||||||
|
Image="plus.png"
|
||||||
|
Clicked="AddButton_Clicked"
|
||||||
|
Style="{StaticResource btn-fab}"
|
||||||
|
AbsoluteLayout.LayoutFlags="PositionProportional"
|
||||||
|
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n AddItem}">
|
||||||
|
<Button.Effects>
|
||||||
|
<effects:FabShadowEffect />
|
||||||
|
</Button.Effects>
|
||||||
|
</Button>
|
||||||
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
</pages:BaseContentPage>
|
||||||
176
src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
Normal file
176
src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
using Bit.App.Resources;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.PlatformConfiguration;
|
||||||
|
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class AuthenticatorPage : BaseContentPage
|
||||||
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly ICipherService _cipherService;
|
||||||
|
private AuthenticatorPageViewModel _vm;
|
||||||
|
private readonly bool _fromTabPage;
|
||||||
|
private readonly Action<string> _selectAction;
|
||||||
|
private readonly TabsPage _tabsPage;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public AuthenticatorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null)
|
||||||
|
{
|
||||||
|
//_tabsPage = tabsPage;
|
||||||
|
InitializeComponent();
|
||||||
|
//_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
|
_vm = BindingContext as AuthenticatorPageViewModel;
|
||||||
|
//_vm.Page = this;
|
||||||
|
//_fromTabPage = fromTabPage;
|
||||||
|
//_selectAction = selectAction;
|
||||||
|
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
_absLayout.Children.Remove(_fab);
|
||||||
|
ToolbarItems.Add(_aboutIconItem);
|
||||||
|
ToolbarItems.Add(_addItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ToolbarItems.Add(_syncItem);
|
||||||
|
ToolbarItems.Add(_lockItem);
|
||||||
|
ToolbarItems.Add(_aboutTextItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
await _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async override void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
//if (!_fromTabPage)
|
||||||
|
//{
|
||||||
|
// await InitAsync();
|
||||||
|
//}
|
||||||
|
//_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
|
||||||
|
//{
|
||||||
|
// if (message.Command == "updatedTheme")
|
||||||
|
// {
|
||||||
|
// Device.BeginInvokeOnMainThread(() =>
|
||||||
|
// {
|
||||||
|
// //_vm.RedrawPassword();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//});
|
||||||
|
|
||||||
|
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||||
|
{
|
||||||
|
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.Message.Contains("No key."))
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(5000);
|
||||||
|
if (!_vm.Loaded)
|
||||||
|
{
|
||||||
|
await _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdjustToolbar();
|
||||||
|
//await CheckAddRequest();
|
||||||
|
}, _mainContent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private async void Search_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
// var page = new SendsPage(_vm.Filter, _vm.Type != null);
|
||||||
|
// await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Sync_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// await _vm.SyncAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Lock_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// await _vaultTimeoutService.LockAsync(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void About_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// _vm.ShowAbout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AddButton_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
// var page = new SendAddEditPage(null, null, _vm.Type);
|
||||||
|
// await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Copy_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
//await _vm.CopyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void More_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
//if (!DoOnce())
|
||||||
|
//{
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
//var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||||
|
// null, AppResources.PasswordHistory);
|
||||||
|
//if (selection == AppResources.PasswordHistory)
|
||||||
|
//{
|
||||||
|
// var page = new GeneratorHistoryPage();
|
||||||
|
// await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
//_broadcasterService.Unsubscribe(nameof(GeneratorPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdjustToolbar()
|
||||||
|
{
|
||||||
|
//_addItem.IsEnabled = !_vm.Deleted;
|
||||||
|
//_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
Normal file
129
src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class AuthenticatorPageListItem : ExtendedViewModel
|
||||||
|
{
|
||||||
|
//private string _totpCode;
|
||||||
|
private readonly ITotpService _totpService;
|
||||||
|
|
||||||
|
//public CipherView Cipher { get; set; }
|
||||||
|
//public CipherType? Type { get; set; }
|
||||||
|
//public int interval { get; set; }
|
||||||
|
//public long TotpSec { get; set; }
|
||||||
|
//private DateTime? _totpInterval = null;
|
||||||
|
|
||||||
|
private CipherView _cipher;
|
||||||
|
|
||||||
|
private bool _websiteIconsEnabled;
|
||||||
|
private string _iconImageSource = string.Empty;
|
||||||
|
|
||||||
|
public int interval { get; set; }
|
||||||
|
private string _totpSec;
|
||||||
|
|
||||||
|
private string _totpCode;
|
||||||
|
private string _totpCodeFormatted = "938 928";
|
||||||
|
|
||||||
|
|
||||||
|
public AuthenticatorPageListItem(CipherView cipherView, bool websiteIconsEnabled)
|
||||||
|
{
|
||||||
|
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
|
||||||
|
Cipher = cipherView;
|
||||||
|
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||||
|
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Command CopyCommand { get; set; }
|
||||||
|
|
||||||
|
public CipherView Cipher
|
||||||
|
{
|
||||||
|
get => _cipher;
|
||||||
|
set => SetProperty(ref _cipher, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TotpCodeFormatted
|
||||||
|
{
|
||||||
|
get => _totpCodeFormatted;
|
||||||
|
set => SetProperty(ref _totpCodeFormatted, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TotpSec
|
||||||
|
{
|
||||||
|
get => _totpSec;
|
||||||
|
set => SetProperty(ref _totpSec, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WebsiteIconsEnabled
|
||||||
|
{
|
||||||
|
get => _websiteIconsEnabled;
|
||||||
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowIconImage
|
||||||
|
{
|
||||||
|
get => WebsiteIconsEnabled
|
||||||
|
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||||
|
&& IconImageSource != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string IconImageSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||||
|
{
|
||||||
|
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||||
|
}
|
||||||
|
return _iconImageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TotpTickAsync()
|
||||||
|
{
|
||||||
|
var epoc = CoreHelpers.EpocUtcNow() / 1000;
|
||||||
|
var mod = epoc % interval;
|
||||||
|
var totpSec = interval - mod;
|
||||||
|
TotpSec = totpSec.ToString();
|
||||||
|
//TotpLow = totpSec < 7;
|
||||||
|
if (mod == 0)
|
||||||
|
{
|
||||||
|
await TotpUpdateCodeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TotpUpdateCodeAsync()
|
||||||
|
{
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
Normal file
153
src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class AuthenticatorPageViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
|
private readonly ITotpService _totpService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private readonly ICipherService _cipherService;
|
||||||
|
|
||||||
|
private bool _showList = true;
|
||||||
|
private bool _refreshing;
|
||||||
|
private bool _loaded;
|
||||||
|
private bool _websiteIconsEnabled = true;
|
||||||
|
//private long _totpSec;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Ctor
|
||||||
|
|
||||||
|
public AuthenticatorPageViewModel()
|
||||||
|
{
|
||||||
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
|
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
|
||||||
|
PageTitle = AppResources.Authenticator;
|
||||||
|
Items = new ExtendedObservableCollection<AuthenticatorPageListItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public async Task CopyAsync()
|
||||||
|
{
|
||||||
|
//await _clipboardService.CopyTextAsync(Password);
|
||||||
|
//_platformUtilsService.ShowToast("success", null,
|
||||||
|
// string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadAsync()
|
||||||
|
{
|
||||||
|
var authed = await _userService.IsAuthenticatedAsync();
|
||||||
|
if (!authed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadDataAsync();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ShowList = true;
|
||||||
|
Refreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadDataAsync()
|
||||||
|
{
|
||||||
|
var _allCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
_allCiphers = _allCiphers.Where(c => c.Type == Core.Enums.CipherType.Login && c.Login.Totp != null).ToList();
|
||||||
|
var filteredCiphers = _allCiphers.Select(c => new AuthenticatorPageListItem(c, WebsiteIconsEnabled)).ToList();
|
||||||
|
Items.ResetWithRange(filteredCiphers);
|
||||||
|
|
||||||
|
foreach (AuthenticatorPageListItem item in Items)
|
||||||
|
{
|
||||||
|
item.TotpUpdateCodeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
//await TotpUpdateCodeAsync();
|
||||||
|
// var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||||
|
// await TotpTickAsync(interval);
|
||||||
|
// _totpInterval = DateTime.UtcNow;
|
||||||
|
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
|
||||||
|
{
|
||||||
|
foreach(AuthenticatorPageListItem item in Items)
|
||||||
|
{
|
||||||
|
item.TotpTickAsync();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
//}
|
||||||
|
|
||||||
|
//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();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
public ExtendedObservableCollection<AuthenticatorPageListItem> Items { get; set; }
|
||||||
|
public Command RefreshCommand { get; set; }
|
||||||
|
|
||||||
|
public bool ShowList
|
||||||
|
{
|
||||||
|
get => _showList;
|
||||||
|
set => SetProperty(ref _showList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Refreshing
|
||||||
|
{
|
||||||
|
get => _refreshing;
|
||||||
|
set => SetProperty(ref _refreshing, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WebsiteIconsEnabled
|
||||||
|
{
|
||||||
|
get => _websiteIconsEnabled;
|
||||||
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Loaded
|
||||||
|
{
|
||||||
|
get => _loaded;
|
||||||
|
set => SetProperty(ref _loaded, value);
|
||||||
|
}
|
||||||
|
//public long TotpSec
|
||||||
|
//{
|
||||||
|
// get => _totpSec;
|
||||||
|
// set => SetProperty(ref _totpSec, value);
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
private NavigationPage _groupingsPage;
|
private NavigationPage _groupingsPage;
|
||||||
|
private NavigationPage _authenticatorPage;
|
||||||
private NavigationPage _sendGroupingsPage;
|
private NavigationPage _sendGroupingsPage;
|
||||||
private NavigationPage _generatorPage;
|
private NavigationPage _generatorPage;
|
||||||
|
|
||||||
@@ -32,8 +33,16 @@ namespace Bit.App.Pages
|
|||||||
Title = AppResources.MyVault,
|
Title = AppResources.MyVault,
|
||||||
IconImageSource = "lock.png"
|
IconImageSource = "lock.png"
|
||||||
};
|
};
|
||||||
|
|
||||||
Children.Add(_groupingsPage);
|
Children.Add(_groupingsPage);
|
||||||
|
|
||||||
|
_authenticatorPage = new NavigationPage(new AuthenticatorPage(true, null, this))
|
||||||
|
{
|
||||||
|
Title = AppResources.Authenticator,
|
||||||
|
IconImageSource = "info.png"
|
||||||
|
};
|
||||||
|
Children.Add(_authenticatorPage);
|
||||||
|
|
||||||
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
|
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
|
||||||
{
|
{
|
||||||
Title = AppResources.Send,
|
Title = AppResources.Send,
|
||||||
|
|||||||
6
src/App/Resources/AppResources.Designer.cs
generated
6
src/App/Resources/AppResources.Designer.cs
generated
@@ -353,6 +353,12 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string Authenticator {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Authenticator", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string Name {
|
public static string Name {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Name", resourceCulture);
|
return ResourceManager.GetString("Name", resourceCulture);
|
||||||
|
|||||||
@@ -299,6 +299,10 @@
|
|||||||
<value>My Vault</value>
|
<value>My Vault</value>
|
||||||
<comment>The title for the vault page.</comment>
|
<comment>The title for the vault page.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Authenticator" xml:space="preserve">
|
||||||
|
<value>Authenticator</value>
|
||||||
|
<comment>Authenticator TOTP feature</comment>
|
||||||
|
</data>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>Name</value>
|
||||||
<comment>Label for an entity name.</comment>
|
<comment>Label for an entity name.</comment>
|
||||||
|
|||||||
Reference in New Issue
Block a user