tearing down event handlers on page disappears

This commit is contained in:
Kyle Spearrin 2017-02-17 00:16:09 -05:00
parent fb564fa817
commit 22f3bd1073
9 changed files with 154 additions and 90 deletions

View file

@ -58,6 +58,7 @@
<Compile Include="Abstractions\Services\ISecureStorageService.cs" /> <Compile Include="Abstractions\Services\ISecureStorageService.cs" />
<Compile Include="Abstractions\Services\ISqlService.cs" /> <Compile Include="Abstractions\Services\ISqlService.cs" />
<Compile Include="Constants.cs" /> <Compile Include="Constants.cs" />
<Compile Include="Controls\ExtendedToolbarItem.cs" />
<Compile Include="Controls\DismissModalToolBarItem.cs" /> <Compile Include="Controls\DismissModalToolBarItem.cs" />
<Compile Include="Controls\ExtendedEditor.cs" /> <Compile Include="Controls\ExtendedEditor.cs" />
<Compile Include="Controls\ExtendedButton.cs" /> <Compile Include="Controls\ExtendedButton.cs" />

View file

@ -4,14 +4,13 @@ using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
public class DismissModalToolBarItem : ToolbarItem, IDisposable public class DismissModalToolBarItem : ExtendedToolbarItem, IDisposable
{ {
private readonly ContentPage _page; private readonly ContentPage _page;
private readonly Action _cancelClickedAction;
public DismissModalToolBarItem(ContentPage page, string text = null, Action cancelClickedAction = null) public DismissModalToolBarItem(ContentPage page, string text = null, Action cancelClickedAction = null)
: base(cancelClickedAction)
{ {
_cancelClickedAction = cancelClickedAction;
_page = page; _page = page;
// TODO: init and dispose events from pages // TODO: init and dispose events from pages
InitEvents(); InitEvents();
@ -19,20 +18,10 @@ namespace Bit.App.Controls
Priority = -1; Priority = -1;
} }
private async void ClickedItem(object sender, EventArgs e) protected async override void ClickedItem(object sender, EventArgs e)
{ {
_cancelClickedAction?.Invoke(); base.ClickedItem(sender, e);
await _page.Navigation.PopModalAsync(); await _page.Navigation.PopModalAsync();
} }
public void InitEvents()
{
Clicked += ClickedItem;
}
public void Dispose()
{
Clicked -= ClickedItem;
}
} }
} }

View file

@ -0,0 +1,30 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedToolbarItem : ToolbarItem, IDisposable
{
public ExtendedToolbarItem(Action clickAction = null)
{
ClickAction = clickAction;
}
public Action ClickAction { get; set; }
protected virtual void ClickedItem(object sender, EventArgs e)
{
ClickAction?.Invoke();
}
public void InitEvents()
{
Clicked += ClickedItem;
}
public void Dispose()
{
Clicked -= ClickedItem;
}
}
}

View file

@ -6,21 +6,17 @@ namespace Bit.App.Controls
{ {
public class VaultListViewCell : LabeledDetailCell public class VaultListViewCell : LabeledDetailCell
{ {
private Action<VaultListPageModel.Login> _moreClickedAction;
public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter), public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter),
typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null); typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null);
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction) public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
{ {
_moreClickedAction = moreClickedAction;
SetBinding(LoginParameterProperty, new Binding(".")); SetBinding(LoginParameterProperty, new Binding("."));
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name); Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name);
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username); Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username);
Button.Image = "more"; Button.Image = "more";
Button.Command = new Command(() => ShowMore()); Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
Button.BackgroundColor = Color.Transparent; Button.BackgroundColor = Color.Transparent;
BackgroundColor = Color.White; BackgroundColor = Color.White;
@ -31,10 +27,5 @@ namespace Bit.App.Controls
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; } get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; }
set { SetValue(LoginParameterProperty, value); } set { SetValue(LoginParameterProperty, value); }
} }
private void ShowMore()
{
_moreClickedAction?.Invoke(LoginParameter);
}
} }
} }

View file

@ -35,6 +35,9 @@ namespace Bit.App.Pages
public FormEntryCell PasswordCell { get; set; } public FormEntryCell PasswordCell { get; set; }
public FormEntryCell ConfirmPasswordCell { get; set; } public FormEntryCell ConfirmPasswordCell { get; set; }
public FormEntryCell PasswordHintCell { get; set; } public FormEntryCell PasswordHintCell { get; set; }
public StackLayout StackLayout { get; set; }
public Label PasswordLabel { get; set; }
public Label HintLabel { get; set; }
private void Init() private void Init()
{ {
@ -71,7 +74,7 @@ namespace Bit.App.Pages
} }
}; };
var passwordLabel = new Label PasswordLabel = new Label
{ {
Text = AppResources.MasterPasswordDescription, Text = AppResources.MasterPasswordDescription,
LineBreakMode = LineBreakMode.WordWrap, LineBreakMode = LineBreakMode.WordWrap,
@ -93,7 +96,7 @@ namespace Bit.App.Pages
} }
}; };
var hintLabel = new Label HintLabel = new Label
{ {
Text = AppResources.MasterPasswordHintDescription, Text = AppResources.MasterPasswordHintDescription,
LineBreakMode = LineBreakMode.WordWrap, LineBreakMode = LineBreakMode.WordWrap,
@ -102,21 +105,15 @@ namespace Bit.App.Pages
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25) Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
}; };
var layout = new StackLayout StackLayout = new StackLayout
{ {
Children = { table, passwordLabel, table2, hintLabel }, Children = { table, PasswordLabel, table2, HintLabel },
Spacing = 0 Spacing = 0
}; };
layout.LayoutChanged += (sender, args) =>
{
passwordLabel.WidthRequest = layout.Bounds.Width - passwordLabel.Bounds.Left * 2;
hintLabel.WidthRequest = layout.Bounds.Width - hintLabel.Bounds.Left * 2;
};
var scrollView = new ScrollView var scrollView = new ScrollView
{ {
Content = layout Content = StackLayout
}; };
var loginToolbarItem = new ToolbarItem(AppResources.Submit, null, async () => var loginToolbarItem = new ToolbarItem(AppResources.Submit, null, async () =>
@ -148,6 +145,7 @@ namespace Bit.App.Pages
PasswordHintCell.InitEvents(); PasswordHintCell.InitEvents();
ConfirmPasswordCell.InitEvents(); ConfirmPasswordCell.InitEvents();
PasswordHintCell.Entry.Completed += Entry_Completed; PasswordHintCell.Entry.Completed += Entry_Completed;
StackLayout.LayoutChanged += Layout_LayoutChanged;
EmailCell.Entry.FocusWithDelay(); EmailCell.Entry.FocusWithDelay();
} }
protected override void OnDisappearing() protected override void OnDisappearing()
@ -158,6 +156,13 @@ namespace Bit.App.Pages
PasswordHintCell.Dispose(); PasswordHintCell.Dispose();
ConfirmPasswordCell.Dispose(); ConfirmPasswordCell.Dispose();
PasswordHintCell.Entry.Completed -= Entry_Completed; PasswordHintCell.Entry.Completed -= Entry_Completed;
StackLayout.LayoutChanged -= Layout_LayoutChanged;
}
private void Layout_LayoutChanged(object sender, EventArgs e)
{
PasswordLabel.WidthRequest = StackLayout.Bounds.Width - PasswordLabel.Bounds.Left * 2;
HintLabel.WidthRequest = StackLayout.Bounds.Width - HintLabel.Bounds.Left * 2;
} }
private async void Entry_Completed(object sender, EventArgs e) private async void Entry_Completed(object sender, EventArgs e)

View file

@ -23,42 +23,37 @@ namespace Bit.App.Pages
Init(); Init();
} }
public ToolsViewCell GeneratorCell { get; set; }
public ToolsViewCell WebCell { get; set; }
public ToolsViewCell ImportCell { get; set; }
public ToolsViewCell ExtensionCell { get; set; }
public ToolsViewCell AutofillCell { get; set; }
public void Init() public void Init()
{ {
var generatorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription, GeneratorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription,
"refresh"); "refresh");
generatorCell.Tapped += GeneratorCell_Tapped; WebCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe");
var webCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe"); ImportCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup");
webCell.Tapped += WebCell_Tapped;
var importCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup");
importCell.Tapped += ImportCell_Tapped;
var section = new TableSection { generatorCell }; var section = new TableSection { GeneratorCell };
if(Device.OS == TargetPlatform.iOS) if(Device.OS == TargetPlatform.iOS)
{ {
var extensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension, ExtensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension,
AppResources.BitwardenAppExtensionDescription, "upload"); AppResources.BitwardenAppExtensionDescription, "upload");
extensionCell.Tapped += (object sender, EventArgs e) => section.Add(ExtensionCell);
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
};
section.Add(extensionCell);
} }
else else
{ {
var autofillServiceCell = new ToolsViewCell( AutofillCell = new ToolsViewCell(
string.Format("{0} ({1})", AppResources.BitwardenAutofillService, AppResources.Beta), string.Format("{0} ({1})", AppResources.BitwardenAutofillService, AppResources.Beta),
AppResources.BitwardenAutofillServiceDescription, "upload"); AppResources.BitwardenAutofillServiceDescription, "upload");
autofillServiceCell.Tapped += (object sender, EventArgs e) => section.Add(AutofillCell);
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage()));
};
section.Add(autofillServiceCell);
} }
section.Add(webCell); section.Add(WebCell);
section.Add(importCell); section.Add(ImportCell);
var table = new ExtendedTableView var table = new ExtendedTableView
{ {
@ -81,6 +76,37 @@ namespace Bit.App.Pages
Content = table; Content = table;
} }
protected override void OnAppearing()
{
base.OnAppearing();
GeneratorCell.Tapped += GeneratorCell_Tapped;
WebCell.Tapped += WebCell_Tapped;
ImportCell.Tapped += ImportCell_Tapped;
ExtensionCell.Tapped += ExtensionCell_Tapped;
AutofillCell.Tapped += AutofillCell_Tapped;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
GeneratorCell.Tapped -= GeneratorCell_Tapped;
WebCell.Tapped -= WebCell_Tapped;
ImportCell.Tapped -= ImportCell_Tapped;
ExtensionCell.Tapped -= ExtensionCell_Tapped;
AutofillCell.Tapped -= AutofillCell_Tapped;
}
private void AutofillCell_Tapped(object sender, EventArgs e)
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage()));
}
private void ExtensionCell_Tapped(object sender, EventArgs e)
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
}
private async void GeneratorCell_Tapped(object sender, EventArgs e) private async void GeneratorCell_Tapped(object sender, EventArgs e)
{ {
await Navigation.PushForDeviceAsync(new ToolsPasswordGeneratorPage()); await Navigation.PushForDeviceAsync(new ToolsPasswordGeneratorPage());

View file

@ -59,6 +59,8 @@ namespace Bit.App.Pages
public StackLayout NoDataStackLayout { get; set; } public StackLayout NoDataStackLayout { get; set; }
public ListView ListView { get; set; } public ListView ListView { get; set; }
public ActivityIndicator LoadingIndicator { get; set; } public ActivityIndicator LoadingIndicator { get; set; }
private SearchToolBarItem SearchItem { get; set; }
private AddLoginToolBarItem AddLoginItem { get; set; }
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; } private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
private IUserDialogs UserDialogs { get; set; } private IUserDialogs UserDialogs { get; set; }
private string Uri { get; set; } private string Uri { get; set; }
@ -88,8 +90,10 @@ namespace Bit.App.Pages
Spacing = 20 Spacing = 20
}; };
ToolbarItems.Add(new AddLoginToolBarItem(this)); AddLoginItem = new AddLoginToolBarItem(this);
ToolbarItems.Add(new SearchToolBarItem(this)); ToolbarItems.Add(AddLoginItem);
SearchItem = new SearchToolBarItem(this);
ToolbarItems.Add(SearchItem);
ListView = new ListView(ListViewCachingStrategy.RecycleElement) ListView = new ListView(ListViewCachingStrategy.RecycleElement)
{ {
@ -106,8 +110,6 @@ namespace Bit.App.Pages
ListView.RowHeight = -1; ListView.RowHeight = -1;
} }
ListView.ItemSelected += LoginSelected;
Title = string.Format(AppResources.LoginsForUri, _name ?? "--"); Title = string.Format(AppResources.LoginsForUri, _name ?? "--");
LoadingIndicator = new ActivityIndicator LoadingIndicator = new ActivityIndicator
@ -123,9 +125,20 @@ namespace Bit.App.Pages
protected override void OnAppearing() protected override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
ListView.ItemSelected += LoginSelected;
AddLoginItem.InitEvents();
SearchItem.InitEvents();
_filterResultsCancellationTokenSource = FetchAndLoadVault(); _filterResultsCancellationTokenSource = FetchAndLoadVault();
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= LoginSelected;
AddLoginItem.Dispose();
SearchItem.Dispose();
}
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
{ {
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
@ -266,26 +279,18 @@ namespace Bit.App.Pages
UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
} }
private class AddLoginToolBarItem : ToolbarItem private class AddLoginToolBarItem : ExtendedToolbarItem
{ {
private readonly VaultAutofillListLoginsPage _page;
public AddLoginToolBarItem(VaultAutofillListLoginsPage page) public AddLoginToolBarItem(VaultAutofillListLoginsPage page)
: base(() => page.AddLoginAsync())
{ {
_page = page;
Text = AppResources.Add; Text = AppResources.Add;
Icon = "plus"; Icon = "plus";
Clicked += ClickedItem;
Priority = 2; Priority = 2;
} }
private void ClickedItem(object sender, EventArgs e)
{
_page.AddLoginAsync();
}
} }
private class SearchToolBarItem : ToolbarItem private class SearchToolBarItem : ExtendedToolbarItem
{ {
private readonly VaultAutofillListLoginsPage _page; private readonly VaultAutofillListLoginsPage _page;
@ -294,11 +299,11 @@ namespace Bit.App.Pages
_page = page; _page = page;
Text = AppResources.Search; Text = AppResources.Search;
Icon = "search"; Icon = "search";
Clicked += ClickedItem;
Priority = 1; Priority = 1;
ClickAction = () => DoClick();
} }
private void ClickedItem(object sender, EventArgs e) private void DoClick()
{ {
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch", _page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch",
_page.Uri.StartsWith("http") ? "Website" : "App"); _page.Uri.StartsWith("http") ? "Website" : "App");

View file

@ -66,6 +66,7 @@ namespace Bit.App.Pages
public StackLayout NoDataStackLayout { get; set; } public StackLayout NoDataStackLayout { get; set; }
public StackLayout ResultsStackLayout { get; set; } public StackLayout ResultsStackLayout { get; set; }
public ActivityIndicator LoadingIndicator { get; set; } public ActivityIndicator LoadingIndicator { get; set; }
private AddLoginToolBarItem AddLoginItem { get; set; }
public string Uri { get; set; } public string Uri { get; set; }
private void Init() private void Init()
@ -80,7 +81,8 @@ namespace Bit.App.Pages
if(!_favorites) if(!_favorites)
{ {
ToolbarItems.Add(new AddLoginToolBarItem(this)); AddLoginItem = new AddLoginToolBarItem(this);
ToolbarItems.Add(AddLoginItem);
} }
ListView = new ListView(ListViewCachingStrategy.RecycleElement) ListView = new ListView(ListViewCachingStrategy.RecycleElement)
@ -98,16 +100,12 @@ namespace Bit.App.Pages
ListView.RowHeight = -1; ListView.RowHeight = -1;
} }
ListView.ItemSelected += LoginSelected;
Search = new SearchBar Search = new SearchBar
{ {
Placeholder = AppResources.SearchVault, Placeholder = AppResources.SearchVault,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)), FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)),
CancelButtonColor = Color.FromHex("3c8dbc") CancelButtonColor = Color.FromHex("3c8dbc")
}; };
Search.TextChanged += SearchBar_TextChanged;
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
// Bug with searchbar on android 7, ref https://bugzilla.xamarin.com/show_bug.cgi?id=43975 // Bug with searchbar on android 7, ref https://bugzilla.xamarin.com/show_bug.cgi?id=43975
if(Device.OS == TargetPlatform.Android && _deviceInfoService.Version >= 24) if(Device.OS == TargetPlatform.Android && _deviceInfoService.Version >= 24)
{ {
@ -231,6 +229,11 @@ namespace Bit.App.Pages
protected override void OnAppearing() protected override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
ListView.ItemSelected += LoginSelected;
Search.TextChanged += SearchBar_TextChanged;
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
AddLoginItem?.InitEvents();
if(_loadExistingData) if(_loadExistingData)
{ {
_filterResultsCancellationTokenSource = FetchAndLoadVault(); _filterResultsCancellationTokenSource = FetchAndLoadVault();
@ -268,6 +271,15 @@ namespace Bit.App.Pages
} }
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= LoginSelected;
Search.TextChanged -= SearchBar_TextChanged;
Search.SearchButtonPressed -= SearchBar_SearchButtonPressed;
AddLoginItem.Dispose();
}
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
{ {
if(string.IsNullOrWhiteSpace(Uri)) if(string.IsNullOrWhiteSpace(Uri))
@ -469,21 +481,16 @@ namespace Bit.App.Pages
await Navigation.PushForDeviceAsync(page); await Navigation.PushForDeviceAsync(page);
} }
private class AddLoginToolBarItem : ToolbarItem private class AddLoginToolBarItem : ExtendedToolbarItem
{ {
private readonly VaultListLoginsPage _page; private readonly VaultListLoginsPage _page;
public AddLoginToolBarItem(VaultListLoginsPage page) public AddLoginToolBarItem(VaultListLoginsPage page)
: base(() => page.AddLogin())
{ {
_page = page; _page = page;
Text = AppResources.Add; Text = AppResources.Add;
Icon = "plus"; Icon = "plus";
Clicked += ClickedItem;
}
private void ClickedItem(object sender, EventArgs e)
{
_page.AddLogin();
} }
} }

View file

@ -6,6 +6,7 @@ using Bit.App.Models.Page;
using Bit.App.Resources; using Bit.App.Resources;
using Xamarin.Forms; using Xamarin.Forms;
using XLabs.Ioc; using XLabs.Ioc;
using System.Threading.Tasks;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -33,10 +34,12 @@ namespace Bit.App.Pages
public LabeledValueCell UsernameCell { get; set; } public LabeledValueCell UsernameCell { get; set; }
public LabeledValueCell PasswordCell { get; set; } public LabeledValueCell PasswordCell { get; set; }
public LabeledValueCell UriCell { get; set; } public LabeledValueCell UriCell { get; set; }
private EditLoginToolBarItem EditItem { get; set; }
private void Init() private void Init()
{ {
ToolbarItems.Add(new EditLoginToolBarItem(this, _loginId)); EditItem = new EditLoginToolBarItem(this, _loginId);
ToolbarItems.Add(EditItem);
if(Device.OS == TargetPlatform.iOS) if(Device.OS == TargetPlatform.iOS)
{ {
ToolbarItems.Add(new DismissModalToolBarItem(this)); ToolbarItems.Add(new DismissModalToolBarItem(this));
@ -121,6 +124,8 @@ namespace Bit.App.Pages
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
EditItem.InitEvents();
var login = await _loginService.GetByIdAsync(_loginId); var login = await _loginService.GetByIdAsync(_loginId);
if(login == null) if(login == null)
{ {
@ -169,13 +174,18 @@ namespace Bit.App.Pages
base.OnAppearing(); base.OnAppearing();
} }
protected override void OnDisappearing()
{
EditItem.Dispose();
}
private void Copy(string copyText, string alertLabel) private void Copy(string copyText, string alertLabel)
{ {
_clipboardService.CopyToClipboard(copyText); _clipboardService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
} }
private class EditLoginToolBarItem : ToolbarItem private class EditLoginToolBarItem : ExtendedToolbarItem
{ {
private readonly VaultViewLoginPage _page; private readonly VaultViewLoginPage _page;
private readonly string _loginId; private readonly string _loginId;
@ -185,10 +195,10 @@ namespace Bit.App.Pages
_page = page; _page = page;
_loginId = loginId; _loginId = loginId;
Text = AppResources.Edit; Text = AppResources.Edit;
Clicked += ClickedItem; ClickAction = async () => await ClickedItem();
} }
private async void ClickedItem(object sender, EventArgs e) private async Task ClickedItem()
{ {
var page = new VaultEditLoginPage(_loginId); var page = new VaultEditLoginPage(_loginId);
await _page.Navigation.PushForDeviceAsync(page); await _page.Navigation.PushForDeviceAsync(page);