diff --git a/src/App/App.csproj b/src/App/App.csproj
index 9f835f135..71b8f9f32 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -37,6 +37,9 @@
GeneratorPage.xaml
+
+ PasswordHistoryPage.xaml
+
ViewPage.xaml
diff --git a/src/App/Pages/Vault/PasswordHistoryPage.xaml b/src/App/Pages/Vault/PasswordHistoryPage.xaml
new file mode 100644
index 000000000..4adc60f61
--- /dev/null
+++ b/src/App/Pages/Vault/PasswordHistoryPage.xaml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs b/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs
new file mode 100644
index 000000000..48fb9a570
--- /dev/null
+++ b/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Bit.App.Pages
+{
+ public partial class PasswordHistoryPage : BaseContentPage
+ {
+ private PasswordHistoryPageViewModel _vm;
+
+ public PasswordHistoryPage(string cipherId)
+ {
+ InitializeComponent();
+ SetActivityIndicator();
+ _vm = BindingContext as PasswordHistoryPageViewModel;
+ _vm.Page = this;
+ _vm.CipherId = cipherId;
+ }
+
+ protected override async void OnAppearing()
+ {
+ base.OnAppearing();
+ await LoadOnAppearedAsync(_mainLayout, true, async () => {
+ await _vm.InitAsync();
+ });
+ }
+ }
+}
diff --git a/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs b/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs
new file mode 100644
index 000000000..49a25495e
--- /dev/null
+++ b/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs
@@ -0,0 +1,53 @@
+using Bit.App.Resources;
+using Bit.Core.Abstractions;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Bit.App.Pages
+{
+ public class PasswordHistoryPageViewModel : BaseViewModel
+ {
+ private readonly IPlatformUtilsService _platformUtilsService;
+ private readonly ICipherService _cipherService;
+
+ private bool _showNoData;
+
+ public PasswordHistoryPageViewModel()
+ {
+ _platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ _cipherService = ServiceContainer.Resolve("cipherService");
+
+ PageTitle = AppResources.PasswordHistory;
+ History = new ExtendedObservableCollection();
+ CopyCommand = new Command(CopyAsync);
+ }
+
+ public Command CopyCommand { get; set; }
+ public string CipherId { get; set; }
+ public ExtendedObservableCollection History { get; set; }
+
+ public bool ShowNoData
+ {
+ get => _showNoData;
+ set => SetProperty(ref _showNoData, value);
+ }
+
+ public async Task InitAsync()
+ {
+ var cipher = await _cipherService.GetAsync(CipherId);
+ var decCipher = await cipher.DecryptAsync();
+ History.ResetWithRange(decCipher.PasswordHistory ?? new List());
+ ShowNoData = History.Count == 0;
+ }
+
+ private async void CopyAsync(PasswordHistoryView ph)
+ {
+ await _platformUtilsService.CopyToClipboardAsync(ph.Password);
+ _platformUtilsService.ShowToast("info", null,
+ string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
+ }
+ }
+}
diff --git a/src/App/Pages/Vault/ViewPage.xaml b/src/App/Pages/Vault/ViewPage.xaml
index 26bea6f75..a13a91355 100644
--- a/src/App/Pages/Vault/ViewPage.xaml
+++ b/src/App/Pages/Vault/ViewPage.xaml
@@ -584,10 +584,18 @@
StyleClass="box-footer-label" />
+ IsVisible="{Binding Cipher.PasswordRevisionDisplayDate, Converter={StaticResource notNull}}">
+
+
+
+
+ IsVisible="{Binding Cipher.HasPasswordHistory}">
+
+
+
+
diff --git a/src/App/Pages/Vault/ViewPage.xaml.cs b/src/App/Pages/Vault/ViewPage.xaml.cs
index c626da79b..dadc6c836 100644
--- a/src/App/Pages/Vault/ViewPage.xaml.cs
+++ b/src/App/Pages/Vault/ViewPage.xaml.cs
@@ -1,6 +1,7 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
+using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -46,5 +47,10 @@ namespace Bit.App.Pages
_broadcasterService.Unsubscribe(nameof(ViewPage));
_vm.CleanUp();
}
+
+ private async void PasswordHistory_Tapped(object sender, System.EventArgs e)
+ {
+ await Navigation.PushModalAsync(new NavigationPage(new PasswordHistoryPage(_vm.CipherId)));
+ }
}
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index c5d60c0a0..3a66e5db8 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -2472,6 +2472,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to No passwords to list..
+ ///
+ public static string NoPasswordsToList {
+ get {
+ return ResourceManager.GetString("NoPasswordsToList", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Notes.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 763df10c4..317605182 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -1393,4 +1393,7 @@
Types
+
+ No passwords to list.
+
\ No newline at end of file
diff --git a/src/App/Styles/Android.xaml b/src/App/Styles/Android.xaml
index b7aa9a338..0479f13b0 100644
--- a/src/App/Styles/Android.xaml
+++ b/src/App/Styles/Android.xaml
@@ -44,6 +44,14 @@
+
diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml
index 935fe0612..bf1835120 100644
--- a/src/App/Styles/Base.xaml
+++ b/src/App/Styles/Base.xaml
@@ -162,6 +162,20 @@
+
diff --git a/src/App/Utilities/DateTimeConverter.cs b/src/App/Utilities/DateTimeConverter.cs
new file mode 100644
index 000000000..bcd9f0258
--- /dev/null
+++ b/src/App/Utilities/DateTimeConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using Xamarin.Forms;
+
+namespace Bit.App.Utilities
+{
+ public class DateTimeConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter,
+ System.Globalization.CultureInfo culture)
+ {
+ if(targetType != typeof(string))
+ {
+ throw new InvalidOperationException("The target must be a string.");
+ }
+ if(value == null)
+ {
+ return string.Empty;
+ }
+ var d = (DateTime)value;
+ return string.Format("{0} {1}", d.ToShortDateString(), d.ToShortTimeString());
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter,
+ System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}