diff --git a/src/App/App.csproj b/src/App/App.csproj index fdcb8b5cb..2fd6a73c9 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -58,6 +58,7 @@ + diff --git a/src/App/Controls/DismissModalToolBarItem.cs b/src/App/Controls/DismissModalToolBarItem.cs index 739f452ba..8e987b2b1 100644 --- a/src/App/Controls/DismissModalToolBarItem.cs +++ b/src/App/Controls/DismissModalToolBarItem.cs @@ -4,14 +4,13 @@ using Xamarin.Forms; namespace Bit.App.Controls { - public class DismissModalToolBarItem : ToolbarItem, IDisposable + public class DismissModalToolBarItem : ExtendedToolbarItem, IDisposable { private readonly ContentPage _page; - private readonly Action _cancelClickedAction; public DismissModalToolBarItem(ContentPage page, string text = null, Action cancelClickedAction = null) + : base(cancelClickedAction) { - _cancelClickedAction = cancelClickedAction; _page = page; // TODO: init and dispose events from pages InitEvents(); @@ -19,20 +18,10 @@ namespace Bit.App.Controls 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(); } - - public void InitEvents() - { - Clicked += ClickedItem; - } - - public void Dispose() - { - Clicked -= ClickedItem; - } } } diff --git a/src/App/Controls/ExtendedToolbarItem.cs b/src/App/Controls/ExtendedToolbarItem.cs new file mode 100644 index 000000000..d6649078a --- /dev/null +++ b/src/App/Controls/ExtendedToolbarItem.cs @@ -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; + } + } +} diff --git a/src/App/Controls/VaultListViewCell.cs b/src/App/Controls/VaultListViewCell.cs index 5733fc4b0..1099d9e7d 100644 --- a/src/App/Controls/VaultListViewCell.cs +++ b/src/App/Controls/VaultListViewCell.cs @@ -6,21 +6,17 @@ namespace Bit.App.Controls { public class VaultListViewCell : LabeledDetailCell { - private Action _moreClickedAction; - public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter), typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null); public VaultListViewCell(Action moreClickedAction) { - _moreClickedAction = moreClickedAction; - SetBinding(LoginParameterProperty, new Binding(".")); Label.SetBinding(Label.TextProperty, s => s.Name); Detail.SetBinding(Label.TextProperty, s => s.Username); Button.Image = "more"; - Button.Command = new Command(() => ShowMore()); + Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter)); Button.BackgroundColor = Color.Transparent; BackgroundColor = Color.White; @@ -31,10 +27,5 @@ namespace Bit.App.Controls get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; } set { SetValue(LoginParameterProperty, value); } } - - private void ShowMore() - { - _moreClickedAction?.Invoke(LoginParameter); - } } } diff --git a/src/App/Pages/RegisterPage.cs b/src/App/Pages/RegisterPage.cs index d4e184c31..c7f8231e0 100644 --- a/src/App/Pages/RegisterPage.cs +++ b/src/App/Pages/RegisterPage.cs @@ -35,6 +35,9 @@ namespace Bit.App.Pages public FormEntryCell PasswordCell { get; set; } public FormEntryCell ConfirmPasswordCell { 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() { @@ -71,7 +74,7 @@ namespace Bit.App.Pages } }; - var passwordLabel = new Label + PasswordLabel = new Label { Text = AppResources.MasterPasswordDescription, LineBreakMode = LineBreakMode.WordWrap, @@ -93,7 +96,7 @@ namespace Bit.App.Pages } }; - var hintLabel = new Label + HintLabel = new Label { Text = AppResources.MasterPasswordHintDescription, LineBreakMode = LineBreakMode.WordWrap, @@ -102,21 +105,15 @@ namespace Bit.App.Pages 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 }; - 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 { - Content = layout + Content = StackLayout }; var loginToolbarItem = new ToolbarItem(AppResources.Submit, null, async () => @@ -148,6 +145,7 @@ namespace Bit.App.Pages PasswordHintCell.InitEvents(); ConfirmPasswordCell.InitEvents(); PasswordHintCell.Entry.Completed += Entry_Completed; + StackLayout.LayoutChanged += Layout_LayoutChanged; EmailCell.Entry.FocusWithDelay(); } protected override void OnDisappearing() @@ -158,6 +156,13 @@ namespace Bit.App.Pages PasswordHintCell.Dispose(); ConfirmPasswordCell.Dispose(); 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) diff --git a/src/App/Pages/Tools/ToolsPage.cs b/src/App/Pages/Tools/ToolsPage.cs index e447c3127..022f0f9f6 100644 --- a/src/App/Pages/Tools/ToolsPage.cs +++ b/src/App/Pages/Tools/ToolsPage.cs @@ -23,42 +23,37 @@ namespace Bit.App.Pages 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() { - var generatorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription, + GeneratorCell = new ToolsViewCell(AppResources.PasswordGenerator, AppResources.PasswordGeneratorDescription, "refresh"); - generatorCell.Tapped += GeneratorCell_Tapped; - var webCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe"); - webCell.Tapped += WebCell_Tapped; - var importCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup"); - importCell.Tapped += ImportCell_Tapped; + WebCell = new ToolsViewCell(AppResources.WebVault, AppResources.WebVaultDescription, "globe"); + ImportCell = new ToolsViewCell(AppResources.ImportLogins, AppResources.ImportLoginsDescription, "cloudup"); - var section = new TableSection { generatorCell }; + var section = new TableSection { GeneratorCell }; if(Device.OS == TargetPlatform.iOS) { - var extensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension, + ExtensionCell = new ToolsViewCell(AppResources.BitwardenAppExtension, AppResources.BitwardenAppExtensionDescription, "upload"); - extensionCell.Tapped += (object sender, EventArgs e) => - { - Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage())); - }; - section.Add(extensionCell); + section.Add(ExtensionCell); } else { - var autofillServiceCell = new ToolsViewCell( + AutofillCell = new ToolsViewCell( string.Format("{0} ({1})", AppResources.BitwardenAutofillService, AppResources.Beta), AppResources.BitwardenAutofillServiceDescription, "upload"); - autofillServiceCell.Tapped += (object sender, EventArgs e) => - { - Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsAutofillServicePage())); - }; - section.Add(autofillServiceCell); + section.Add(AutofillCell); } - section.Add(webCell); - section.Add(importCell); + section.Add(WebCell); + section.Add(ImportCell); var table = new ExtendedTableView { @@ -81,6 +76,37 @@ namespace Bit.App.Pages 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) { await Navigation.PushForDeviceAsync(new ToolsPasswordGeneratorPage()); diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs index 7349746b7..6e5a5788e 100644 --- a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs @@ -59,6 +59,8 @@ namespace Bit.App.Pages public StackLayout NoDataStackLayout { get; set; } public ListView ListView { get; set; } public ActivityIndicator LoadingIndicator { get; set; } + private SearchToolBarItem SearchItem { get; set; } + private AddLoginToolBarItem AddLoginItem { get; set; } private IGoogleAnalyticsService GoogleAnalyticsService { get; set; } private IUserDialogs UserDialogs { get; set; } private string Uri { get; set; } @@ -88,8 +90,10 @@ namespace Bit.App.Pages Spacing = 20 }; - ToolbarItems.Add(new AddLoginToolBarItem(this)); - ToolbarItems.Add(new SearchToolBarItem(this)); + AddLoginItem = new AddLoginToolBarItem(this); + ToolbarItems.Add(AddLoginItem); + SearchItem = new SearchToolBarItem(this); + ToolbarItems.Add(SearchItem); ListView = new ListView(ListViewCachingStrategy.RecycleElement) { @@ -106,8 +110,6 @@ namespace Bit.App.Pages ListView.RowHeight = -1; } - ListView.ItemSelected += LoginSelected; - Title = string.Format(AppResources.LoginsForUri, _name ?? "--"); LoadingIndicator = new ActivityIndicator @@ -123,9 +125,20 @@ namespace Bit.App.Pages protected override void OnAppearing() { base.OnAppearing(); + ListView.ItemSelected += LoginSelected; + AddLoginItem.InitEvents(); + SearchItem.InitEvents(); _filterResultsCancellationTokenSource = FetchAndLoadVault(); } + protected override void OnDisappearing() + { + base.OnDisappearing(); + ListView.ItemSelected -= LoginSelected; + AddLoginItem.Dispose(); + SearchItem.Dispose(); + } + protected override bool OnBackButtonPressed() { GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); @@ -266,26 +279,18 @@ namespace Bit.App.Pages UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); } - private class AddLoginToolBarItem : ToolbarItem + private class AddLoginToolBarItem : ExtendedToolbarItem { - private readonly VaultAutofillListLoginsPage _page; - public AddLoginToolBarItem(VaultAutofillListLoginsPage page) + : base(() => page.AddLoginAsync()) { - _page = page; Text = AppResources.Add; Icon = "plus"; - Clicked += ClickedItem; Priority = 2; } - - private void ClickedItem(object sender, EventArgs e) - { - _page.AddLoginAsync(); - } } - private class SearchToolBarItem : ToolbarItem + private class SearchToolBarItem : ExtendedToolbarItem { private readonly VaultAutofillListLoginsPage _page; @@ -294,11 +299,11 @@ namespace Bit.App.Pages _page = page; Text = AppResources.Search; Icon = "search"; - Clicked += ClickedItem; Priority = 1; + ClickAction = () => DoClick(); } - private void ClickedItem(object sender, EventArgs e) + private void DoClick() { _page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch", _page.Uri.StartsWith("http") ? "Website" : "App"); diff --git a/src/App/Pages/Vault/VaultListLoginsPage.cs b/src/App/Pages/Vault/VaultListLoginsPage.cs index 4246297cc..d66d42594 100644 --- a/src/App/Pages/Vault/VaultListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultListLoginsPage.cs @@ -66,6 +66,7 @@ namespace Bit.App.Pages public StackLayout NoDataStackLayout { get; set; } public StackLayout ResultsStackLayout { get; set; } public ActivityIndicator LoadingIndicator { get; set; } + private AddLoginToolBarItem AddLoginItem { get; set; } public string Uri { get; set; } private void Init() @@ -80,7 +81,8 @@ namespace Bit.App.Pages if(!_favorites) { - ToolbarItems.Add(new AddLoginToolBarItem(this)); + AddLoginItem = new AddLoginToolBarItem(this); + ToolbarItems.Add(AddLoginItem); } ListView = new ListView(ListViewCachingStrategy.RecycleElement) @@ -98,16 +100,12 @@ namespace Bit.App.Pages ListView.RowHeight = -1; } - ListView.ItemSelected += LoginSelected; - Search = new SearchBar { Placeholder = AppResources.SearchVault, FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)), 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 if(Device.OS == TargetPlatform.Android && _deviceInfoService.Version >= 24) { @@ -231,6 +229,11 @@ namespace Bit.App.Pages protected override void OnAppearing() { base.OnAppearing(); + ListView.ItemSelected += LoginSelected; + Search.TextChanged += SearchBar_TextChanged; + Search.SearchButtonPressed += SearchBar_SearchButtonPressed; + AddLoginItem?.InitEvents(); + if(_loadExistingData) { _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() { if(string.IsNullOrWhiteSpace(Uri)) @@ -469,21 +481,16 @@ namespace Bit.App.Pages await Navigation.PushForDeviceAsync(page); } - private class AddLoginToolBarItem : ToolbarItem + private class AddLoginToolBarItem : ExtendedToolbarItem { private readonly VaultListLoginsPage _page; public AddLoginToolBarItem(VaultListLoginsPage page) + : base(() => page.AddLogin()) { _page = page; Text = AppResources.Add; Icon = "plus"; - Clicked += ClickedItem; - } - - private void ClickedItem(object sender, EventArgs e) - { - _page.AddLogin(); } } diff --git a/src/App/Pages/Vault/VaultViewLoginPage.cs b/src/App/Pages/Vault/VaultViewLoginPage.cs index d8a00a43f..cb928b5bb 100644 --- a/src/App/Pages/Vault/VaultViewLoginPage.cs +++ b/src/App/Pages/Vault/VaultViewLoginPage.cs @@ -6,6 +6,7 @@ using Bit.App.Models.Page; using Bit.App.Resources; using Xamarin.Forms; using XLabs.Ioc; +using System.Threading.Tasks; namespace Bit.App.Pages { @@ -33,10 +34,12 @@ namespace Bit.App.Pages public LabeledValueCell UsernameCell { get; set; } public LabeledValueCell PasswordCell { get; set; } public LabeledValueCell UriCell { get; set; } + private EditLoginToolBarItem EditItem { get; set; } private void Init() { - ToolbarItems.Add(new EditLoginToolBarItem(this, _loginId)); + EditItem = new EditLoginToolBarItem(this, _loginId); + ToolbarItems.Add(EditItem); if(Device.OS == TargetPlatform.iOS) { ToolbarItems.Add(new DismissModalToolBarItem(this)); @@ -121,6 +124,8 @@ namespace Bit.App.Pages protected async override void OnAppearing() { + EditItem.InitEvents(); + var login = await _loginService.GetByIdAsync(_loginId); if(login == null) { @@ -169,13 +174,18 @@ namespace Bit.App.Pages base.OnAppearing(); } + protected override void OnDisappearing() + { + EditItem.Dispose(); + } + private void Copy(string copyText, string alertLabel) { _clipboardService.CopyToClipboard(copyText); _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); } - private class EditLoginToolBarItem : ToolbarItem + private class EditLoginToolBarItem : ExtendedToolbarItem { private readonly VaultViewLoginPage _page; private readonly string _loginId; @@ -185,10 +195,10 @@ namespace Bit.App.Pages _page = page; _loginId = loginId; 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); await _page.Navigation.PushForDeviceAsync(page);