diff --git a/src/App/App.csproj b/src/App/App.csproj
index f2dde2180..9c88afb94 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -63,6 +63,7 @@
+
@@ -98,7 +99,7 @@
-
+
diff --git a/src/App/Models/View/VaultView.cs b/src/App/Models/Page/VaultListPageModel.cs
similarity index 89%
rename from src/App/Models/View/VaultView.cs
rename to src/App/Models/Page/VaultListPageModel.cs
index 1c334c71b..4e8092c18 100644
--- a/src/App/Models/View/VaultView.cs
+++ b/src/App/Models/Page/VaultListPageModel.cs
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using Bit.App.Resources;
-namespace Bit.App.Models.View
+namespace Bit.App.Models.Page
{
- public class VaultView
+ public class VaultListPageModel
{
public class Site
{
@@ -44,7 +45,7 @@ namespace Bit.App.Models.View
}
public string Id { get; set; }
- public string Name { get; set; } = "(none)";
+ public string Name { get; set; } = AppResources.FolderNone;
public string FirstLetter { get { return Name.Substring(0, 1); } }
}
}
diff --git a/src/App/Models/Page/VaultViewSitePageModel.cs b/src/App/Models/Page/VaultViewSitePageModel.cs
new file mode 100644
index 000000000..130ffef5a
--- /dev/null
+++ b/src/App/Models/Page/VaultViewSitePageModel.cs
@@ -0,0 +1,93 @@
+using System;
+using System.ComponentModel;
+using Bit.App.Resources;
+
+namespace Bit.App.Models.Page
+{
+ public class VaultViewSitePageModel : INotifyPropertyChanged
+ {
+ private string _name;
+ private string _username;
+ private string _password;
+ private string _uri;
+ private string _notes;
+ private bool _showPassword;
+
+ public VaultViewSitePageModel() { }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ _name = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(PageTitle)));
+ }
+ }
+ public string Username
+ {
+ get { return _username; }
+ set
+ {
+ _username = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Username)));
+ }
+ }
+ public string Password
+ {
+ get { return _password; }
+ set
+ {
+ _password = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Password)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword)));
+ }
+ }
+ public string Uri
+ {
+ get { return _uri; }
+ set
+ {
+ _uri = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Uri)));
+ }
+ }
+ public string Notes
+ {
+ get { return _notes; }
+ set
+ {
+ _notes = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Notes)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowNotes)));
+ }
+ }
+ public string PageTitle => Name ?? AppResources.SiteNoName;
+ public bool ShowPassword
+ {
+ get { return _showPassword; }
+ set
+ {
+ _showPassword = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowPassword)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideText)));
+ }
+ }
+ public string MaskedPassword => ShowPassword ? Password : Password == null ? null : new string('●', Password.Length);
+ public string ShowHideText => ShowPassword ? AppResources.Hide : AppResources.Show;
+ public bool ShowNotes => !string.IsNullOrWhiteSpace(Notes);
+
+ public void Update(Site site)
+ {
+ Name = site.Name?.Decrypt();
+ Username = site.Username?.Decrypt();
+ Password = site.Password?.Decrypt();
+ Uri = site.Uri?.Decrypt();
+ Notes = site.Notes?.Decrypt();
+ }
+ }
+}
diff --git a/src/App/Pages/VaultAddSitePage.cs b/src/App/Pages/VaultAddSitePage.cs
index f6af6c801..0bb4c1cdf 100644
--- a/src/App/Pages/VaultAddSitePage.cs
+++ b/src/App/Pages/VaultAddSitePage.cs
@@ -1,8 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Reflection.Emit;
-using System.Text;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Models;
@@ -15,19 +12,26 @@ namespace Bit.App.Pages
{
public class VaultAddSitePage : ContentPage
{
+ private readonly ISiteService _siteService;
+ private readonly IFolderService _folderService;
+ private readonly IUserDialogs _userDialogs;
+ private readonly IConnectivity _connectivity;
+
public VaultAddSitePage()
{
- var cryptoService = Resolver.Resolve();
- var siteService = Resolver.Resolve();
- var folderService = Resolver.Resolve();
- var userDialogs = Resolver.Resolve();
- var connectivity = Resolver.Resolve();
+ _siteService = Resolver.Resolve();
+ _folderService = Resolver.Resolve();
+ _userDialogs = Resolver.Resolve();
+ _connectivity = Resolver.Resolve();
+ }
- var folders = folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
+ private void Init()
+ {
+ var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
var uriEntry = new Entry { Keyboard = Keyboard.Url };
var nameEntry = new Entry();
- var folderPicker = new Picker { Title = "Folder" };
+ var folderPicker = new Picker { Title = AppResources.Folder };
folderPicker.Items.Add(AppResources.FolderNone);
folderPicker.SelectedIndex = 0;
foreach(var folder in folders)
@@ -60,7 +64,7 @@ namespace Bit.App.Pages
var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () =>
{
- if(!connectivity.IsConnected)
+ if(!_connectivity.IsConnected)
{
AlertNoConnection();
return;
@@ -92,26 +96,26 @@ namespace Bit.App.Pages
site.FolderId = folders.ElementAt(folderPicker.SelectedIndex - 1).Id;
}
- var saveTask = siteService.SaveAsync(site);
- userDialogs.ShowLoading("Saving...", MaskType.Black);
+ var saveTask = _siteService.SaveAsync(site);
+ _userDialogs.ShowLoading("Saving...", MaskType.Black);
await saveTask;
- userDialogs.HideLoading();
+ _userDialogs.HideLoading();
await Navigation.PopAsync();
- userDialogs.SuccessToast(nameEntry.Text, "New site created.");
+ _userDialogs.SuccessToast(nameEntry.Text, "New site created.");
}, ToolbarItemOrder.Default, 0);
Title = AppResources.AddSite;
Content = scrollView;
ToolbarItems.Add(saveToolBarItem);
- if(!connectivity.IsConnected)
+ if(!_connectivity.IsConnected)
{
AlertNoConnection();
}
}
- public void AlertNoConnection()
+ private void AlertNoConnection()
{
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
}
diff --git a/src/App/Pages/VaultEditSitePage.cs b/src/App/Pages/VaultEditSitePage.cs
index b9758567e..1a690f645 100644
--- a/src/App/Pages/VaultEditSitePage.cs
+++ b/src/App/Pages/VaultEditSitePage.cs
@@ -3,17 +3,139 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
-
+using Acr.UserDialogs;
+using Bit.App.Abstractions;
+using Bit.App.Resources;
+using Plugin.Connectivity.Abstractions;
using Xamarin.Forms;
+using XLabs.Ioc;
namespace Bit.App.Pages
{
public class VaultEditSitePage : ContentPage
{
+ private readonly string _siteId;
+ private readonly ISiteService _siteService;
+ private readonly IFolderService _folderService;
+ private readonly IUserDialogs _userDialogs;
+ private readonly IConnectivity _connectivity;
+
public VaultEditSitePage(string siteId)
{
- Title = "Edit Site " + siteId;
- Content = null;
+ _siteId = siteId;
+ _siteService = Resolver.Resolve();
+ _folderService = Resolver.Resolve();
+ _userDialogs = Resolver.Resolve();
+ _connectivity = Resolver.Resolve();
+
+ Init();
+ }
+
+ private void Init()
+ {
+ var site = _siteService.GetByIdAsync(_siteId).GetAwaiter().GetResult();
+ if(site == null)
+ {
+ // TODO: handle error. navigate back? should never happen...
+ return;
+ }
+
+ var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
+
+ var uriEntry = new Entry { Keyboard = Keyboard.Url, Text = site.Uri?.Decrypt() };
+ var nameEntry = new Entry { Text = site.Name?.Decrypt() };
+ var folderPicker = new Picker { Title = AppResources.Folder };
+ folderPicker.Items.Add(AppResources.FolderNone);
+ int selectedIndex = 0;
+ int i = 0;
+ foreach(var folder in folders)
+ {
+ i++;
+ if(folder.Id == site.FolderId)
+ {
+ selectedIndex = i;
+ }
+
+ folderPicker.Items.Add(folder.Name.Decrypt());
+ }
+ folderPicker.SelectedIndex = selectedIndex;
+ var usernameEntry = new Entry { Text = site.Username?.Decrypt() };
+ var passwordEntry = new Entry { IsPassword = true, Text = site.Password?.Decrypt() };
+ var notesEditor = new Editor { Text = site.Notes?.Decrypt() };
+
+ var stackLayout = new StackLayout();
+ stackLayout.Children.Add(new Label { Text = AppResources.URI });
+ stackLayout.Children.Add(uriEntry);
+ stackLayout.Children.Add(new Label { Text = AppResources.Name });
+ stackLayout.Children.Add(nameEntry);
+ stackLayout.Children.Add(new Label { Text = AppResources.Folder });
+ stackLayout.Children.Add(folderPicker);
+ stackLayout.Children.Add(new Label { Text = AppResources.Username });
+ stackLayout.Children.Add(usernameEntry);
+ stackLayout.Children.Add(new Label { Text = AppResources.Password });
+ stackLayout.Children.Add(passwordEntry);
+ stackLayout.Children.Add(new Label { Text = AppResources.Notes });
+ stackLayout.Children.Add(notesEditor);
+
+ var scrollView = new ScrollView
+ {
+ Content = stackLayout,
+ Orientation = ScrollOrientation.Vertical
+ };
+
+ var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () =>
+ {
+ if(!_connectivity.IsConnected)
+ {
+ AlertNoConnection();
+ return;
+ }
+
+ if(string.IsNullOrWhiteSpace(uriEntry.Text))
+ {
+ await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.URI), AppResources.Ok);
+ return;
+ }
+
+ if(string.IsNullOrWhiteSpace(nameEntry.Text))
+ {
+ await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Name), AppResources.Ok);
+ return;
+ }
+
+ site.Uri = uriEntry.Text.Encrypt();
+ site.Name = nameEntry.Text.Encrypt();
+ site.Username = usernameEntry.Text?.Encrypt();
+ site.Password = passwordEntry.Text?.Encrypt();
+ site.Notes = notesEditor.Text?.Encrypt();
+
+ if(folderPicker.SelectedIndex > 0)
+ {
+ site.FolderId = folders.ElementAt(folderPicker.SelectedIndex - 1).Id;
+ }
+
+ var saveTask = _siteService.SaveAsync(site);
+ _userDialogs.ShowLoading("Saving...", MaskType.Black);
+ await saveTask;
+
+ _userDialogs.HideLoading();
+ await Navigation.PopAsync();
+ _userDialogs.SuccessToast(nameEntry.Text, "Site updated.");
+ }, ToolbarItemOrder.Default, 0);
+
+ Title = "Edit Site";
+ Content = scrollView;
+ ToolbarItems.Add(saveToolBarItem);
+
+ if(!_connectivity.IsConnected)
+ {
+ AlertNoConnection();
+ }
+ }
+
+ private void AlertNoConnection()
+ {
+ DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
}
}
}
diff --git a/src/App/Pages/VaultListPage.cs b/src/App/Pages/VaultListPage.cs
index c0011a94d..661dae3d1 100644
--- a/src/App/Pages/VaultListPage.cs
+++ b/src/App/Pages/VaultListPage.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
-using Bit.App.Models.View;
+using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
@@ -28,7 +28,7 @@ namespace Bit.App.Pages
Init();
}
- public ObservableCollection Folders { get; private set; } = new ObservableCollection();
+ public ObservableCollection Folders { get; private set; } = new ObservableCollection();
private void Init()
{
@@ -41,7 +41,7 @@ namespace Bit.App.Pages
Title = AppResources.MyVault;
Content = listView;
- NavigationPage.SetBackButtonTitle(this, string.Empty);
+ NavigationPage.SetBackButtonTitle(this, "Back");
}
protected override void OnAppearing()
@@ -59,25 +59,25 @@ namespace Bit.App.Pages
foreach(var folder in folders)
{
- var f = new VaultView.Folder(folder, sites.Where(s => s.FolderId == folder.Id));
+ var f = new VaultListPageModel.Folder(folder, sites.Where(s => s.FolderId == folder.Id));
Folders.Add(f);
}
// add the sites with no folder
- var noneFolder = new VaultView.Folder(sites.Where(s => s.FolderId == null));
+ var noneFolder = new VaultListPageModel.Folder(sites.Where(s => s.FolderId == null));
Folders.Add(noneFolder);
}
private void SiteSelected(object sender, SelectedItemChangedEventArgs e)
{
- var site = e.SelectedItem as VaultView.Site;
+ var site = e.SelectedItem as VaultListPageModel.Site;
Navigation.PushAsync(new VaultViewSitePage(site.Id));
}
private async void MoreClickedAsync(object sender, EventArgs e)
{
var mi = sender as MenuItem;
- var site = mi.CommandParameter as VaultView.Site;
+ var site = mi.CommandParameter as VaultListPageModel.Site;
var selection = await DisplayActionSheet(AppResources.MoreOptions, AppResources.Cancel, null,
AppResources.View, AppResources.Edit, AppResources.CopyPassword, AppResources.CopyUsername, AppResources.GoToWebsite);
@@ -117,7 +117,7 @@ namespace Bit.App.Pages
}
var mi = sender as MenuItem;
- var site = mi.CommandParameter as VaultView.Site;
+ var site = mi.CommandParameter as VaultListPageModel.Site;
var deleteCall = await _siteService.DeleteAsync(site.Id);
if(deleteCall.Succeeded)
@@ -163,8 +163,8 @@ namespace Bit.App.Pages
deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
deleteAction.Clicked += page.DeleteClickedAsync;
- this.SetBinding(TextProperty, s => s.Name);
- this.SetBinding(DetailProperty, s => s.Username);
+ this.SetBinding(TextProperty, s => s.Name);
+ this.SetBinding(DetailProperty, s => s.Username);
ContextActions.Add(moreAction);
ContextActions.Add(deleteAction);
}
diff --git a/src/App/Pages/VaultViewSitePage.cs b/src/App/Pages/VaultViewSitePage.cs
index e526e6204..f2fb4cbdd 100644
--- a/src/App/Pages/VaultViewSitePage.cs
+++ b/src/App/Pages/VaultViewSitePage.cs
@@ -1,10 +1,7 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection.Emit;
-using System.Text;
using Acr.UserDialogs;
using Bit.App.Abstractions;
+using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
@@ -28,25 +25,22 @@ namespace Bit.App.Pages
Init();
}
- public void Init()
+ private VaultViewSitePageModel Model { get; set; } = new VaultViewSitePageModel();
+
+ private void Init()
{
ToolbarItems.Add(new EditSiteToolBarItem(this, _siteId));
+ var stackLayout = new StackLayout();
- var site = _siteService.GetByIdAsync(_siteId).GetAwaiter().GetResult();
- if(site == null)
- {
- // TODO: handle error. navigate back? should never happen...
- return;
- }
-
+ // Username
var usernameRow = new StackLayout { Orientation = StackOrientation.Horizontal };
var usernameLabel = new Label
{
- Text = site.Username?.Decrypt(),
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation
};
+ usernameLabel.SetBinding(Label.TextProperty, s => s.Username);
usernameRow.Children.Add(usernameLabel);
usernameRow.Children.Add(new Button
{
@@ -55,63 +49,66 @@ namespace Bit.App.Pages
VerticalOptions = LayoutOptions.Center,
Command = new Command(() => Copy(usernameLabel.Text, AppResources.Username))
});
+ stackLayout.Children.Add(new Label { Text = AppResources.Username });
+ stackLayout.Children.Add(usernameRow);
+ // Password
var passwordRow = new StackLayout { Orientation = StackOrientation.Horizontal };
- var password = site.Password?.Decrypt();
var passwordLabel = new Label
{
- Text = new string('●', password.Length),
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation
};
+ passwordLabel.SetBinding(Label.TextProperty, s => s.MaskedPassword);
passwordRow.Children.Add(passwordLabel);
var togglePasswordButton = new Button
{
- Text = AppResources.Show,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center,
- Command = new Command((self) => TogglePassword(self as Button, passwordLabel, password))
+ Command = new Command(() => Model.ShowPassword = !Model.ShowPassword)
};
togglePasswordButton.CommandParameter = togglePasswordButton;
+ togglePasswordButton.SetBinding(Button.TextProperty, s => s.ShowHideText);
passwordRow.Children.Add(togglePasswordButton);
passwordRow.Children.Add(new Button
{
Text = AppResources.Copy,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center,
- Command = new Command(() => Copy(password, AppResources.Password))
+ Command = new Command(() => Copy(Model.Password, AppResources.Password))
});
+ stackLayout.Children.Add(new Label { Text = AppResources.Password });
+ stackLayout.Children.Add(passwordRow);
+ // URI
var uriRow = new StackLayout { Orientation = StackOrientation.Horizontal };
- var uri = site.Uri?.Decrypt();
- uriRow.Children.Add(new Label
+ var uriLabel = new Label
{
- Text = uri,
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation
- });
+ };
+ uriLabel.SetBinding(Label.TextProperty, s => s.Uri);
+ uriRow.Children.Add(uriLabel);
uriRow.Children.Add(new Button
{
Text = AppResources.Launch,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center,
- Command = new Command(() => Device.OpenUri(new Uri(uri)))
+ Command = new Command(() => Device.OpenUri(new Uri(uriLabel.Text)))
});
-
- var stackLayout = new StackLayout();
- stackLayout.Children.Add(new Label { Text = AppResources.Username });
- stackLayout.Children.Add(usernameRow);
- stackLayout.Children.Add(new Label { Text = AppResources.Password });
- stackLayout.Children.Add(passwordRow);
stackLayout.Children.Add(new Label { Text = AppResources.Website });
stackLayout.Children.Add(uriRow);
- if(site.Notes != null)
- {
- stackLayout.Children.Add(new Label { Text = AppResources.Notes });
- stackLayout.Children.Add(new Label { Text = site.Notes.Decrypt() });
- }
+
+ // Notes
+ var notes = new Label { Text = AppResources.Notes };
+ notes.SetBinding(Label.IsVisibleProperty, s => s.ShowNotes);
+ stackLayout.Children.Add(notes);
+ var notesLabel = new Label();
+ notesLabel.SetBinding(Label.TextProperty, s => s.Notes);
+ notesLabel.SetBinding(Label.IsVisibleProperty, s => s.ShowNotes);
+ stackLayout.Children.Add(notesLabel);
var scrollView = new ScrollView
{
@@ -119,22 +116,21 @@ namespace Bit.App.Pages
Orientation = ScrollOrientation.Vertical
};
- Title = site.Name?.Decrypt() ?? AppResources.SiteNoName;
+ SetBinding(Page.TitleProperty, new Binding("PageTitle"));
Content = scrollView;
+ BindingContext = Model;
}
- private void TogglePassword(Button toggleButton, Label passwordLabel, string password)
+ protected override void OnAppearing()
{
- if(toggleButton.Text == AppResources.Show)
+ var site = _siteService.GetByIdAsync(_siteId).GetAwaiter().GetResult();
+ if(site == null)
{
- toggleButton.Text = AppResources.Hide;
- passwordLabel.Text = password;
- }
- else
- {
- toggleButton.Text = AppResources.Show;
- passwordLabel.Text = new string('●', password.Length);
+ // TODO: handle error. navigate back? should never happen...
+ return;
}
+
+ Model.Update(site);
}
private void Copy(string copyText, string alertLabel)