Moved add/edit pages to use custom form cells. Moved navigation of vault to modals. Created custom renderer for left modal dismiss button on navigation pages. refresh for edit site UI.

This commit is contained in:
Kyle Spearrin 2016-05-13 00:11:32 -04:00
parent 8ec957c39c
commit 83f308cbf0
13 changed files with 340 additions and 103 deletions

View file

@ -44,12 +44,17 @@
<Compile Include="Behaviors\EmailValidationBehavior.cs" />
<Compile Include="Behaviors\ConnectivityBehavior.cs" />
<Compile Include="Behaviors\RequiredValidationBehavior.cs" />
<Compile Include="Controls\DismissModalToolBarItem.cs" />
<Compile Include="Controls\EntryLabel.cs" />
<Compile Include="Controls\ExtendedEditor.cs" />
<Compile Include="Controls\ExtendedNavigationPage.cs" />
<Compile Include="Controls\ExtendedTableView.cs" />
<Compile Include="Controls\ExtendedPicker.cs" />
<Compile Include="Controls\ExtendedEntry.cs" />
<Compile Include="Controls\ExtendedTabbedPage.cs" />
<Compile Include="Controls\FormEditorCell.cs" />
<Compile Include="Controls\FormPickerCell.cs" />
<Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Models\Api\ApiError.cs" />
<Compile Include="Models\Api\ApiResult.cs" />
<Compile Include="Models\Api\Request\FolderRequest.cs" />

View file

@ -0,0 +1,23 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class DismissModalToolBarItem : ToolbarItem
{
private readonly ContentPage _page;
public DismissModalToolBarItem(ContentPage page, string text = null)
{
_page = page;
Text = text ?? "Close";
Clicked += ClickedItem;
Priority = -1;
}
private async void ClickedItem(object sender, EventArgs e)
{
await _page.Navigation.PopModalAsync();
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedNavigationPage : NavigationPage
{
public ExtendedNavigationPage()
: base()
{
SetDefaults();
}
public ExtendedNavigationPage(Page root)
: base(root)
{
SetDefaults();
}
private void SetDefaults()
{
// default colors for our app
BarBackgroundColor = Color.FromHex("3c8dbc");
BarTextColor = Color.FromHex("ffffff");
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class FormEditorCell : ViewCell
{
public FormEditorCell(Keyboard entryKeyboard = null, double? height = null)
{
Editor = new ExtendedEditor
{
Keyboard = entryKeyboard,
HasBorder = false
};
if(height.HasValue)
{
Editor.HeightRequest = height.Value;
}
var stackLayout = new StackLayout
{
Padding = new Thickness(15, 15, 15, 0),
BackgroundColor = Color.White
};
stackLayout.Children.Add(Editor);
View = stackLayout;
}
public ExtendedEditor Editor { get; private set; }
}
}

View file

@ -0,0 +1,38 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class FormEntryCell : ViewCell
{
public FormEntryCell(string labelText, Keyboard entryKeyboard = null, bool IsPassword = false)
{
Label = new Label
{
Text = labelText,
FontSize = 14,
TextColor = Color.FromHex("777777")
};
Entry = new ExtendedEntry
{
Keyboard = entryKeyboard,
HasBorder = false
};
var stackLayout = new StackLayout
{
Padding = new Thickness(15, 15, 15, 0),
BackgroundColor = Color.White
};
stackLayout.Children.Add(Label);
stackLayout.Children.Add(Entry);
View = stackLayout;
}
public Label Label { get; private set; }
public ExtendedEntry Entry { get; private set; }
}
}

View file

@ -0,0 +1,44 @@
using System;
using Bit.App.Resources;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class FormPickerCell : ViewCell
{
public FormPickerCell(string labelText, string[] pickerItems)
{
Label = new Label
{
Text = labelText,
FontSize = 14,
TextColor = Color.FromHex("777777")
};
Picker = new ExtendedPicker
{
HasBorder = false
};
foreach(var item in pickerItems)
{
Picker.Items.Add(item);
}
Picker.SelectedIndex = 0;
var stackLayout = new StackLayout
{
Padding = new Thickness(15, 15, 15, 0),
BackgroundColor = Color.White
};
stackLayout.Children.Add(Label);
stackLayout.Children.Add(Picker);
View = stackLayout;
}
public Label Label { get; private set; }
public ExtendedPicker Picker { get; private set; }
}
}

View file

@ -12,12 +12,9 @@ namespace Bit.App.Pages
BarTintColor = Color.FromHex("222d32");
TintColor = Color.FromHex("ffffff");
var settingsNavigation = new NavigationPage(new SettingsPage());
var vaultNavigation = new NavigationPage(new VaultListPage());
var syncNavigation = new NavigationPage(new SyncPage());
vaultNavigation.BarBackgroundColor = settingsNavigation.BarBackgroundColor = syncNavigation.BarBackgroundColor = Color.FromHex("3c8dbc");
vaultNavigation.BarTextColor = settingsNavigation.BarTextColor = syncNavigation.BarTextColor = Color.FromHex("ffffff");
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
var vaultNavigation = new ExtendedNavigationPage(new VaultListPage());
var syncNavigation = new ExtendedNavigationPage(new SyncPage());
vaultNavigation.Title = AppResources.MyVault;
vaultNavigation.Icon = "fa-lock";

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Acr.UserDialogs;
using Bit.App.Abstractions;
@ -30,55 +31,20 @@ namespace Bit.App.Pages
private void Init()
{
var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
var uriCell = new FormEntryCell(AppResources.URI, Keyboard.Url);
var nameCell = new FormEntryCell(AppResources.Name);
var usernameCell = new FormEntryCell(AppResources.Username);
var passwordCell = new FormEntryCell(AppResources.Password, IsPassword: true);
var uriEntry = new ExtendedEntry { Keyboard = Keyboard.Url, HasBorder = false };
var nameEntry = new ExtendedEntry { HasBorder = false };
var folderPicker = new ExtendedPicker { Title = AppResources.Folder, HasBorder = false };
folderPicker.Items.Add(AppResources.FolderNone);
folderPicker.SelectedIndex = 0;
var folderOptions = new List<string> { AppResources.FolderNone };
var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
foreach(var folder in folders)
{
folderPicker.Items.Add(folder.Name.Decrypt());
folderOptions.Add(folder.Name.Decrypt());
}
var usernameEntry = new ExtendedEntry { HasBorder = false };
var passwordEntry = new ExtendedEntry { IsPassword = true, HasBorder = false };
var notesEditor = new ExtendedEditor { HeightRequest = 90, HasBorder = false };
var folderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
var uriStackLayout = new FormEntryStackLayout();
uriStackLayout.Children.Add(new EntryLabel { Text = AppResources.URI });
uriStackLayout.Children.Add(uriEntry);
var uriCell = new ViewCell();
uriCell.View = uriStackLayout;
var nameStackLayout = new FormEntryStackLayout();
nameStackLayout.Children.Add(new EntryLabel { Text = AppResources.Name });
nameStackLayout.Children.Add(nameEntry);
var nameCell = new ViewCell();
nameCell.View = nameStackLayout;
var folderStackLayout = new FormEntryStackLayout();
folderStackLayout.Children.Add(new EntryLabel { Text = AppResources.Folder });
folderStackLayout.Children.Add(folderPicker);
var folderCell = new ViewCell();
folderCell.View = folderStackLayout;
var usernameStackLayout = new FormEntryStackLayout();
usernameStackLayout.Children.Add(new EntryLabel { Text = AppResources.Username });
usernameStackLayout.Children.Add(usernameEntry);
var usernameCell = new ViewCell();
usernameCell.View = usernameStackLayout;
var passwordStackLayout = new FormEntryStackLayout();
passwordStackLayout.Children.Add(new EntryLabel { Text = AppResources.Password });
passwordStackLayout.Children.Add(passwordEntry);
var passwordCell = new ViewCell();
passwordCell.View = passwordStackLayout;
var notesStackLayout = new FormEntryStackLayout();
notesStackLayout.Children.Add(notesEditor);
var notesCell = new ViewCell();
notesCell.View = notesStackLayout;
var notesCell = new FormEditorCell(height:90);
var mainTable = new ExtendedTableView
{
@ -88,7 +54,7 @@ namespace Bit.App.Pages
EnableSelection = false,
Root = new TableRoot
{
new TableSection
new TableSection("Site Information")
{
uriCell,
nameCell,
@ -123,13 +89,13 @@ namespace Bit.App.Pages
return;
}
if(string.IsNullOrWhiteSpace(uriEntry.Text))
if(string.IsNullOrWhiteSpace(uriCell.Entry.Text))
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.URI), AppResources.Ok);
return;
}
if(string.IsNullOrWhiteSpace(nameEntry.Text))
if(string.IsNullOrWhiteSpace(nameCell.Entry.Text))
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Name), AppResources.Ok);
return;
@ -137,16 +103,16 @@ namespace Bit.App.Pages
var site = new Site
{
Uri = uriEntry.Text.Encrypt(),
Name = nameEntry.Text.Encrypt(),
Username = usernameEntry.Text?.Encrypt(),
Password = passwordEntry.Text?.Encrypt(),
Notes = notesEditor.Text?.Encrypt(),
Uri = uriCell.Entry.Text.Encrypt(),
Name = nameCell.Entry.Text.Encrypt(),
Username = usernameCell.Entry.Text?.Encrypt(),
Password = passwordCell.Entry.Text?.Encrypt(),
Notes = notesCell.Editor.Text?.Encrypt(),
};
if(folderPicker.SelectedIndex > 0)
if(folderCell.Picker.SelectedIndex > 0)
{
site.FolderId = folders.ElementAt(folderPicker.SelectedIndex - 1).Id;
site.FolderId = folders.ElementAt(folderCell.Picker.SelectedIndex - 1).Id;
}
var saveTask = _siteService.SaveAsync(site);
@ -155,12 +121,13 @@ namespace Bit.App.Pages
_userDialogs.HideLoading();
await Navigation.PopAsync();
_userDialogs.SuccessToast(nameEntry.Text, "New site created.");
_userDialogs.SuccessToast(nameCell.Entry.Text, "New site created.");
}, ToolbarItemOrder.Default, 0);
Title = AppResources.AddSite;
Content = scrollView;
ToolbarItems.Add(saveToolBarItem);
ToolbarItems.Add(new DismissModalToolBarItem(this, "Cancel"));
if(!_connectivity.IsConnected)
{

View file

@ -1,10 +1,9 @@
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.Controls;
using Bit.App.Resources;
using Plugin.Connectivity.Abstractions;
using Xamarin.Forms;
@ -40,12 +39,17 @@ namespace Bit.App.Pages
return;
}
var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
var uriCell = new FormEntryCell(AppResources.URI, Keyboard.Url);
uriCell.Entry.Text = site.Uri?.Decrypt();
var nameCell = new FormEntryCell(AppResources.Name);
nameCell.Entry.Text = site.Name?.Decrypt();
var usernameCell = new FormEntryCell(AppResources.Username);
usernameCell.Entry.Text = site.Username?.Decrypt();
var passwordCell = new FormEntryCell(AppResources.Password, IsPassword: true);
passwordCell.Entry.Text = site.Password?.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);
var folderOptions = new List<string> { AppResources.FolderNone };
var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt());
int selectedIndex = 0;
int i = 0;
foreach(var folder in folders)
@ -56,30 +60,46 @@ namespace Bit.App.Pages
selectedIndex = i;
}
folderPicker.Items.Add(folder.Name.Decrypt());
folderOptions.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 folderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray());
folderCell.Picker.SelectedIndex = selectedIndex;
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 notesCell = new FormEditorCell(height: 90);
notesCell.Editor.Text = site.Notes?.Decrypt();
var table = new ExtendedTableView
{
Intent = TableIntent.Settings,
EnableScrolling = false,
HasUnevenRows = true,
EnableSelection = false,
Root = new TableRoot
{
new TableSection("Site Information")
{
uriCell,
nameCell,
folderCell,
usernameCell,
passwordCell
},
new TableSection(AppResources.Notes)
{
notesCell
}
}
};
if(Device.OS == TargetPlatform.iOS)
{
table.RowHeight = -1;
table.EstimatedRowHeight = 70;
}
var scrollView = new ScrollView
{
Content = stackLayout,
Content = table,
Orientation = ScrollOrientation.Vertical
};
@ -91,27 +111,31 @@ namespace Bit.App.Pages
return;
}
if(string.IsNullOrWhiteSpace(uriEntry.Text))
if(string.IsNullOrWhiteSpace(uriCell.Entry.Text))
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.URI), AppResources.Ok);
return;
}
if(string.IsNullOrWhiteSpace(nameEntry.Text))
if(string.IsNullOrWhiteSpace(nameCell.Entry.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();
site.Uri = uriCell.Entry.Text.Encrypt();
site.Name = nameCell.Entry.Text.Encrypt();
site.Username = usernameCell.Entry.Text?.Encrypt();
site.Password = passwordCell.Entry.Text?.Encrypt();
site.Notes = notesCell.Editor.Text?.Encrypt();
if(folderPicker.SelectedIndex > 0)
if(folderCell.Picker.SelectedIndex > 0)
{
site.FolderId = folders.ElementAt(folderPicker.SelectedIndex - 1).Id;
site.FolderId = folders.ElementAt(folderCell.Picker.SelectedIndex - 1).Id;
}
else
{
site.FolderId = null;
}
var saveTask = _siteService.SaveAsync(site);
@ -119,13 +143,14 @@ namespace Bit.App.Pages
await saveTask;
_userDialogs.HideLoading();
await Navigation.PopAsync();
_userDialogs.SuccessToast(nameEntry.Text, "Site updated.");
await Navigation.PopModalAsync();
_userDialogs.SuccessToast(nameCell.Entry.Text, "Site updated.");
}, ToolbarItemOrder.Default, 0);
Title = "Edit Site";
Content = scrollView;
ToolbarItems.Add(saveToolBarItem);
ToolbarItems.Add(new DismissModalToolBarItem(this, "Cancel"));
if(!_connectivity.IsConnected)
{

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
@ -71,7 +72,8 @@ namespace Bit.App.Pages
private void SiteSelected(object sender, SelectedItemChangedEventArgs e)
{
var site = e.SelectedItem as VaultListPageModel.Site;
Navigation.PushAsync(new VaultViewSitePage(site.Id));
var page = new ExtendedNavigationPage(new VaultViewSitePage(site.Id));
Navigation.PushModalAsync(page);
}
private async void MoreClickedAsync(object sender, EventArgs e)
@ -83,11 +85,13 @@ namespace Bit.App.Pages
if(selection == AppResources.View)
{
await Navigation.PushAsync(new VaultViewSitePage(site.Id));
var page = new ExtendedNavigationPage(new VaultViewSitePage(site.Id));
await Navigation.PushModalAsync(page);
}
else if(selection == AppResources.Edit)
{
// TODO: navigate to edit page
var page = new ExtendedNavigationPage(new VaultEditSitePage(site.Id));
await Navigation.PushModalAsync(page);
}
else if(selection == AppResources.CopyPassword)
{
@ -147,7 +151,8 @@ namespace Bit.App.Pages
private async void ClickedItem(object sender, EventArgs e)
{
await _page.Navigation.PushAsync(new VaultAddSitePage());
var page = new ExtendedNavigationPage(new VaultAddSitePage());
await _page.Navigation.PushModalAsync(page);
}
}

View file

@ -1,6 +1,7 @@
using System;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
@ -30,6 +31,7 @@ namespace Bit.App.Pages
private void Init()
{
ToolbarItems.Add(new EditSiteToolBarItem(this, _siteId));
ToolbarItems.Add(new DismissModalToolBarItem(this));
var stackLayout = new StackLayout();
// Username
@ -157,7 +159,8 @@ namespace Bit.App.Pages
private async void ClickedItem(object sender, EventArgs e)
{
await _page.Navigation.PushAsync(new VaultEditSitePage(_siteId));
var page = new ExtendedNavigationPage(new VaultEditSitePage(_siteId));
await _page.Navigation.PushModalAsync(page);
}
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Bit.iOS.Controls;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ContentPage), typeof(ContentPageRenderer))]
namespace Bit.iOS.Controls
{
public class ContentPageRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var contentPage = Element as ContentPage;
if(contentPage == null || NavigationController == null)
{
return;
}
var itemsInfo = contentPage.ToolbarItems;
var navigationItem = NavigationController.TopViewController.NavigationItem;
var leftNativeButtons = (navigationItem.LeftBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
var rightNativeButtons = (navigationItem.RightBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
var newLeftButtons = new List<UIBarButtonItem>();
var newRightButtons = new List<UIBarButtonItem>();
rightNativeButtons.ForEach(nativeItem =>
{
// Use reflection to get Xamarin private field "_item"
var field = nativeItem.GetType().GetField("_item", BindingFlags.NonPublic | BindingFlags.Instance);
if(field == null)
{
return;
}
var info = field.GetValue(nativeItem) as ToolbarItem;
if(info == null)
{
return;
}
if(info.Priority < 0)
{
newLeftButtons.Add(nativeItem);
}
else
{
newRightButtons.Add(nativeItem);
}
});
leftNativeButtons.ForEach(nativeItem =>
{
newLeftButtons.Add(nativeItem);
});
navigationItem.RightBarButtonItems = newRightButtons.ToArray();
navigationItem.LeftBarButtonItems = newLeftButtons.ToArray();
}
}
}

View file

@ -101,6 +101,7 @@
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
</PropertyGroup>
<ItemGroup>
<Compile Include="Controls\ContentPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
<Compile Include="Controls\ExtendedPickerRenderer.cs" />
<Compile Include="Controls\ExtendedEntryRenderer.cs" />