using System; using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Models.Page; using Bit.App.Resources; using Plugin.Settings.Abstractions; using Xamarin.Forms; using XLabs.Ioc; using Bit.App.Utilities; namespace Bit.App.Pages { public class ToolsPasswordGeneratorPage : ExtendedContentPage { private readonly IPasswordGenerationService _passwordGenerationService; private readonly ISettings _settings; private readonly IDeviceActionService _deviceActionService; private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly Action _passwordValueAction; private readonly bool _fromAutofill; private readonly MainPage _mainPage; public ToolsPasswordGeneratorPage(Action passwordValueAction = null, bool fromAutofill = false) { _passwordGenerationService = Resolver.Resolve(); _settings = Resolver.Resolve(); _deviceActionService = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); _passwordValueAction = passwordValueAction; _fromAutofill = fromAutofill; Init(); } public ToolsPasswordGeneratorPage(MainPage mainPage) : this() { _mainPage = mainPage; } public PasswordGeneratorPageModel Model { get; private set; } = new PasswordGeneratorPageModel(); public Label Password { get; private set; } public SliderViewCell SliderCell { get; private set; } public TapGestureRecognizer Tgr { get; set; } public ExtendedTextCell RegenerateCell { get; set; } public ExtendedTextCell CopyCell { get; set; } public ExtendedSwitchCell UppercaseCell { get; set; } public ExtendedSwitchCell LowercaseCell { get; set; } public ExtendedSwitchCell SpecialCell { get; set; } public ExtendedSwitchCell NumbersCell { get; set; } public ExtendedSwitchCell AvoidAmbiguousCell { get; set; } public StepperCell SpecialMinCell { get; set; } public StepperCell NumbersMinCell { get; set; } public void Init() { Password = new Label { FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)), Margin = new Thickness(15, 30, 15, 30), HorizontalTextAlignment = TextAlignment.Center, FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier"), LineBreakMode = LineBreakMode.TailTruncation, VerticalOptions = LayoutOptions.Start, TextColor = Color.Black }; Tgr = new TapGestureRecognizer(); Password.GestureRecognizers.Add(Tgr); Password.SetBinding(Label.TextProperty, nameof(PasswordGeneratorPageModel.Password)); SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings); RegenerateCell = new ExtendedTextCell { Text = AppResources.RegeneratePassword, TextColor = Colors.Primary }; CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = Colors.Primary }; UppercaseCell = new ExtendedSwitchCell { Text = "A-Z", On = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true) }; LowercaseCell = new ExtendedSwitchCell { Text = "a-z", On = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true) }; SpecialCell = new ExtendedSwitchCell { Text = "!@#$%^&*", On = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true) }; NumbersCell = new ExtendedSwitchCell { Text = "0-9", On = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true) }; AvoidAmbiguousCell = new ExtendedSwitchCell { Text = AppResources.AvoidAmbiguousCharacters, On = !_settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false) }; NumbersMinCell = new StepperCell(AppResources.MinNumbers, _settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1), 0, 5, 1, () => { _settings.AddOrUpdateValue(Constants.PasswordGeneratorMinNumbers, Convert.ToInt32(NumbersMinCell.Stepper.Value)); Model.Password = _passwordGenerationService.GeneratePassword(); }); SpecialMinCell = new StepperCell(AppResources.MinSpecial, _settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1), 0, 5, 1, () => { _settings.AddOrUpdateValue(Constants.PasswordGeneratorMinSpecial, Convert.ToInt32(SpecialMinCell.Stepper.Value)); Model.Password = _passwordGenerationService.GeneratePassword(); }); var table = new ExtendedTableView { VerticalOptions = LayoutOptions.Start, EnableScrolling = false, Intent = TableIntent.Settings, HasUnevenRows = true, NoHeader = true, Root = new TableRoot { new TableSection(Helpers.GetEmptyTableSectionTitle()) { RegenerateCell, CopyCell }, new TableSection(AppResources.Options) { SliderCell, UppercaseCell, LowercaseCell, NumbersCell, SpecialCell }, new TableSection(Helpers.GetEmptyTableSectionTitle()) { NumbersMinCell, SpecialMinCell }, new TableSection(Helpers.GetEmptyTableSectionTitle()) { AvoidAmbiguousCell } } }; if(Device.RuntimePlatform == Device.iOS) { table.RowHeight = -1; table.EstimatedRowHeight = 44; if(_passwordValueAction != null) { ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel)); } } else if(Device.RuntimePlatform == Device.Android) { table.BottomPadding = 50; } var stackLayout = new RedrawableStackLayout { Orientation = StackOrientation.Vertical, Children = { Password, table }, VerticalOptions = LayoutOptions.FillAndExpand, Spacing = 0 }; table.WrappingStackLayout = () => stackLayout; var scrollView = new ScrollView { Content = stackLayout, Orientation = ScrollOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; if(_passwordValueAction != null) { var selectToolBarItem = new ToolbarItem(AppResources.Select, Helpers.ToolbarImage("ion_chevron_right.png"), async () => { if(_fromAutofill) { _googleAnalyticsService.TrackExtensionEvent("SelectedGeneratedPassword"); } else { _googleAnalyticsService.TrackAppEvent("SelectedGeneratedPassword"); } _passwordValueAction(Password.Text); await Navigation.PopForDeviceAsync(); }, ToolbarItemOrder.Default, 0); ToolbarItems.Add(selectToolBarItem); } Title = AppResources.PasswordGenerator; Content = scrollView; BindingContext = Model; } private void Tgr_Tapped(object sender, EventArgs e) { CopyPassword(); } protected override void OnAppearing() { base.OnAppearing(); Tgr.Tapped += Tgr_Tapped; RegenerateCell.Tapped += RegenerateCell_Tapped; CopyCell.Tapped += CopyCell_Tapped; SliderCell.InitEvents(); SpecialCell.OnChanged += SpecialCell_OnChanged; AvoidAmbiguousCell.OnChanged += AvoidAmbiguousCell_OnChanged; UppercaseCell.OnChanged += UppercaseCell_OnChanged; LowercaseCell.OnChanged += LowercaseCell_OnChanged; NumbersCell.OnChanged += NumbersCell_OnChanged; NumbersMinCell.InitEvents(); SpecialMinCell.InitEvents(); if(_fromAutofill) { _googleAnalyticsService.TrackExtensionEvent("GeneratedPassword"); } else { _googleAnalyticsService.TrackAppEvent("GeneratedPassword"); } Model.Password = _passwordGenerationService.GeneratePassword(); Model.Length = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10).ToString(); } protected override void OnDisappearing() { base.OnDisappearing(); Tgr.Tapped -= Tgr_Tapped; RegenerateCell.Tapped -= RegenerateCell_Tapped; SpecialCell.OnChanged -= SpecialCell_OnChanged; AvoidAmbiguousCell.OnChanged -= AvoidAmbiguousCell_OnChanged; UppercaseCell.OnChanged -= UppercaseCell_OnChanged; LowercaseCell.OnChanged -= LowercaseCell_OnChanged; NumbersCell.OnChanged -= NumbersCell_OnChanged; NumbersMinCell.Dispose(); SpecialMinCell.Dispose(); CopyCell.Tapped -= CopyCell_Tapped; SliderCell.Dispose(); } protected override bool OnBackButtonPressed() { if(Device.RuntimePlatform == Device.Android && _mainPage != null) { _mainPage.ResetToVaultPage(); return true; } return base.OnBackButtonPressed(); } private void RegenerateCell_Tapped(object sender, EventArgs e) { Model.Password = _passwordGenerationService.GeneratePassword(); if(_fromAutofill) { _googleAnalyticsService.TrackExtensionEvent("RegeneratedPassword"); } else { _googleAnalyticsService.TrackAppEvent("RegeneratedPassword"); } } private void CopyCell_Tapped(object sender, EventArgs e) { CopyPassword(); } private void CopyPassword() { if(_fromAutofill) { _googleAnalyticsService.TrackExtensionEvent("CopiedGeneratedPassword"); } else { _googleAnalyticsService.TrackAppEvent("CopiedGeneratedPassword"); } _deviceActionService.CopyToClipboard(Password.Text); _deviceActionService.Toast(string.Format(AppResources.ValueHasBeenCopied, AppResources.Password)); } private void AvoidAmbiguousCell_OnChanged(object sender, ToggledEventArgs e) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorAmbiguous, !AvoidAmbiguousCell.On); Model.Password = _passwordGenerationService.GeneratePassword(); } private void NumbersCell_OnChanged(object sender, ToggledEventArgs e) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorNumbers, NumbersCell.On); if(InvalidState()) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true); LowercaseCell.On = true; } Model.Password = _passwordGenerationService.GeneratePassword(); } private void SpecialCell_OnChanged(object sender, ToggledEventArgs e) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorSpecial, SpecialCell.On); if(InvalidState()) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true); LowercaseCell.On = true; } Model.Password = _passwordGenerationService.GeneratePassword(); } private void LowercaseCell_OnChanged(object sender, ToggledEventArgs e) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, LowercaseCell.On); if(InvalidState()) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, true); UppercaseCell.On = true; } Model.Password = _passwordGenerationService.GeneratePassword(); } private void UppercaseCell_OnChanged(object sender, ToggledEventArgs e) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, UppercaseCell.On); if(InvalidState()) { _settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true); LowercaseCell.On = true; } Model.Password = _passwordGenerationService.GeneratePassword(); } private bool InvalidState() { return !LowercaseCell.On && !UppercaseCell.On && !NumbersCell.On && !SpecialCell.On; } // TODO: move to standalone reusable control public class SliderViewCell : ExtendedViewCell, IDisposable { private readonly ToolsPasswordGeneratorPage _page; private readonly IPasswordGenerationService _passwordGenerationService; private readonly ISettings _settings; public Label Value { get; set; } public Slider LengthSlider { get; set; } public SliderViewCell( ToolsPasswordGeneratorPage page, IPasswordGenerationService passwordGenerationService, ISettings settings) { _page = page; _passwordGenerationService = passwordGenerationService; _settings = settings; var label = new Label { FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)), Text = AppResources.Length, HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.CenterAndExpand }; LengthSlider = new Slider(5, 64, _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10)) { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.CenterAndExpand, MaximumTrackColor = Color.LightGray, MinimumTrackColor = Color.LightGray, }; Value = new Label { FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)), HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.CenterAndExpand, Style = (Style)Application.Current.Resources["text-muted"], FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier"), }; Value.SetBinding(Label.TextProperty, nameof(PasswordGeneratorPageModel.Length)); var stackLayout = new StackLayout { Orientation = StackOrientation.Horizontal, Spacing = 15, Children = { label, LengthSlider, Value }, Padding = Helpers.OnPlatform( iOS: new Thickness(15, 8), Android: new Thickness(16, 10), Windows: new Thickness(15, 8)) }; stackLayout.AdjustPaddingForDevice(); if(Device.RuntimePlatform == Device.Android) { label.TextColor = Color.Black; } View = stackLayout; } private void Slider_ValueChanged(object sender, ValueChangedEventArgs e) { var length = Convert.ToInt32(LengthSlider.Value); _settings.AddOrUpdateValue(Constants.PasswordGeneratorLength, length); _page.Model.Length = length.ToString(); _page.Model.Password = _passwordGenerationService.GeneratePassword(); } public void InitEvents() { LengthSlider.ValueChanged += Slider_ValueChanged; } public void Dispose() { LengthSlider.ValueChanged -= Slider_ValueChanged; } } } }