diff --git a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
index 4d4e82554..2c5a31042 100644
--- a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
+++ b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
@@ -3,35 +3,92 @@
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="controls:AuthenticatorViewCellViewModel">
+ x:DataType="pages:AuthenticatorPageListItem">
+
+
+
+
+
+
+
-
+
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
index 871c8a424..08a96fb72 100644
--- a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
+++ b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
@@ -1,5 +1,7 @@
using System;
+using Bit.App.Utilities;
using Bit.Core.Models.View;
+using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
@@ -7,64 +9,112 @@ 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.OneWay);
+ 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 ButtonCommandProperty = BindableProperty.Create(
- nameof(ButtonCommand), typeof(Command), 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), 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 CipherView Cipher
+ public long TotpSec
{
- get => GetValue(CipherProperty) as CipherView;
- set => SetValue(CipherProperty, value);
+ get => (long)GetValue(TotpSecProperty);
+ set => SetValue(TotpSecProperty, value);
}
- public Command ButtonCommand
+ public bool ShowIconImage
{
- get => GetValue(ButtonCommandProperty) as Command;
- set => SetValue(ButtonCommandProperty, value);
+ get => WebsiteIconsEnabled ?? false
+ && !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
+ && IconImageSource != null;
}
- protected override void OnPropertyChanged(string propertyName = null)
+ private string _iconImageSource = string.Empty;
+ public string IconImageSource
{
- base.OnPropertyChanged(propertyName);
- if (propertyName == CipherProperty.PropertyName)
+ get
{
- if (Cipher == null)
+ if (_iconImageSource == string.Empty) // default value since icon source can return null
{
- return;
+ _iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
- BindingContext = new AuthenticatorViewCellViewModel(Cipher, WebsiteIconsEnabled ?? false);
- }
- else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
- {
- if (Cipher == null)
- {
- return;
- }
- ((AuthenticatorViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
+ return _iconImageSource;
}
+
}
+ private string _totpCodeFormatted = "938 928";
+ public string TotpCodeFormatted
+ {
+ get => _totpCodeFormatted;
+ set => _totpCodeFormatted = value;
+ }
+
+
+ //public Command ButtonCommand
+ //{
+ // get => GetValue(ButtonCommandProperty) as Command;
+ // 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);
+ //ButtonCommand?.Execute(cipher);
}
}
}
diff --git a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCellViewModel.cs b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCellViewModel.cs
index 0010e6a3b..ad1aaf5e2 100644
--- a/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCellViewModel.cs
+++ b/src/App/Controls/AuthenticatorViewCell/AuthenticatorViewCellViewModel.cs
@@ -1,3 +1,4 @@
+using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@@ -8,7 +9,7 @@ namespace Bit.App.Controls
public class AuthenticatorViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
- private string _totpCodeFormatted = "938928";
+ private string _totpCodeFormatted = "938 928";
private string _totpSec;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
@@ -64,6 +65,39 @@ namespace Bit.App.Controls
}
}
+
+ 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;
+ // }
+ //}
}
}
diff --git a/src/App/Pages/Authenticator/AuthenticatorPage.xaml b/src/App/Pages/Authenticator/AuthenticatorPage.xaml
index fde715061..9eb346a34 100644
--- a/src/App/Pages/Authenticator/AuthenticatorPage.xaml
+++ b/src/App/Pages/Authenticator/AuthenticatorPage.xaml
@@ -8,7 +8,8 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:AuthenticatorPageViewModel"
- Title="{Binding PageTitle}">
+ Title="{Binding PageTitle}"
+ x:Name="_page">
@@ -40,31 +41,31 @@
AutomationProperties.Name="{u:I18n AddItem}" />
-
+ x:DataType="pages:AuthenticatorPageListItem">
+
-
+
+
+
+
+
+
-
-
-
diff --git a/src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs b/src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
index f21e464b1..2ca8971eb 100644
--- a/src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
+++ b/src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
@@ -1,5 +1,6 @@
using Bit.App.Resources;
using System;
+using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@@ -14,6 +15,8 @@ namespace Bit.App.Pages
#region Members
private readonly IBroadcasterService _broadcasterService;
+ private readonly ISyncService _syncService;
+ private readonly ICipherService _cipherService;
private AuthenticatorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action _selectAction;
@@ -26,6 +29,8 @@ namespace Bit.App.Pages
//_tabsPage = tabsPage;
InitializeComponent();
//_broadcasterService = ServiceContainer.Resolve("broadcasterService");
+ _syncService = ServiceContainer.Resolve("syncService");
+ _cipherService = ServiceContainer.Resolve("cipherService");
_vm = BindingContext as AuthenticatorPageViewModel;
//_vm.Page = this;
//_fromTabPage = fromTabPage;
@@ -47,7 +52,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
- await _vm.InitAsync();
+ await _vm.LoadAsync();
}
protected async override void OnAppearing()
@@ -67,7 +72,36 @@ namespace Bit.App.Pages
// });
// }
//});
+
+ 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)
@@ -132,6 +166,11 @@ namespace Bit.App.Pages
base.OnDisappearing();
//_broadcasterService.Unsubscribe(nameof(GeneratorPage));
}
-
+
+ private void AdjustToolbar()
+ {
+ //_addItem.IsEnabled = !_vm.Deleted;
+ //_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
+ }
}
}
diff --git a/src/App/Pages/Authenticator/AuthenticatorPageListItem.cs b/src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
new file mode 100644
index 000000000..5475a9a90
--- /dev/null
+++ b/src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
@@ -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("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;
+ }
+ }
+ }
+}
diff --git a/src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs b/src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
index 49f12075a..ffda90d0b 100644
--- a/src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
+++ b/src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
@@ -1,7 +1,9 @@
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;
@@ -12,29 +14,35 @@ namespace Bit.App.Pages
#region Members
private readonly IClipboardService _clipboardService;
- private bool _showList = true;
- private bool _refreshing;
+ 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("cipherService");
_userService = ServiceContainer.Resolve("userService");
_vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService");
+ _totpService = ServiceContainer.Resolve("totpService");
PageTitle = AppResources.Authenticator;
+ Items = new ExtendedObservableCollection();
}
#endregion
#region Methods
- public async Task InitAsync() { }
-
public async Task CopyAsync()
{
//await _clipboardService.CopyTextAsync(Password);
@@ -54,15 +62,61 @@ namespace Bit.App.Pages
return;
}
- this.ShowList = true;
- this.Refreshing = false;
+ 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 Items { get; set; }
+ public ExtendedObservableCollection Items { get; set; }
public Command RefreshCommand { get; set; }
public bool ShowList
@@ -77,6 +131,23 @@ namespace Bit.App.Pages
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
}
}