mirror of
https://github.com/bitwarden/mobile
synced 2026-01-08 03:23:23 +00:00
reset for v2
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using FFImageLoading.Forms;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsAboutPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IAppInfoService _appInfoService;
|
||||
|
||||
public SettingsAboutPage()
|
||||
{
|
||||
_appInfoService = Resolver.Resolve<IAppInfoService>();
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell CreditsCell { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var logo = new CachedImage
|
||||
{
|
||||
Source = "logo.png",
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
WidthRequest = 282,
|
||||
HeightRequest = 44
|
||||
};
|
||||
|
||||
var versionLabel = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
Text = $@"{AppResources.Version} {_appInfoService.Version} ({_appInfoService.Build})
|
||||
© 8bit Solutions LLC 2015-{DateTime.Now.Year}",
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
var logoVersionStackLayout = new StackLayout
|
||||
{
|
||||
Children = { logo, versionLabel },
|
||||
Spacing = 20,
|
||||
Padding = new Thickness(0, 40, 0, 0)
|
||||
};
|
||||
|
||||
CreditsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Credits,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
EnableScrolling = false,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
CreditsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 44;
|
||||
}
|
||||
|
||||
var stackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { logoVersionStackLayout, table },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
table.WrappingStackLayout = () => stackLayout;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.About;
|
||||
Content = new ScrollView { Content = stackLayout };
|
||||
}
|
||||
|
||||
private void RateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushAsync(new SettingsCreditsPage());
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
CreditsCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
CreditsCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Linq;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsAddFolderPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public SettingsAddFolderPage()
|
||||
{
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
EnableScrolling = false,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
NameCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
table.BottomPadding = 50;
|
||||
}
|
||||
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
var folder = new Folder
|
||||
{
|
||||
Name = NameCell.Entry.Text.Encrypt()
|
||||
};
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveResult.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderCreated);
|
||||
_googleAnalyticsService.TrackAppEvent("CreatedFolder");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveResult.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveResult.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
Title = AppResources.AddFolder;
|
||||
Content = table;
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsCreditsPage : ExtendedContentPage
|
||||
{
|
||||
public SettingsCreditsPage()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var table = new ExtendedTableView
|
||||
{
|
||||
EnableScrolling = true,
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
EnableSelection = false,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.Translations)
|
||||
{
|
||||
new CustomViewCell(@"@felixqu - Chinese Simplified
|
||||
@thomassth - Chinese Traditional
|
||||
@Primokorn, @maxlandry - French
|
||||
@bestHippos - Italian
|
||||
@SW1FT - Portuguese
|
||||
@majod - Slovak
|
||||
@King-Tut-Tut - Swedish
|
||||
@Igetin - Finnish")
|
||||
},
|
||||
new TableSection(AppResources.Icons)
|
||||
{
|
||||
new CustomViewCell(@"Tools by Alex Auda Samora from the Noun Project
|
||||
Fingerprint by masterpage.com from the Noun Project")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
table.RowHeight = -1;
|
||||
table.EstimatedRowHeight = 100;
|
||||
}
|
||||
|
||||
Title = AppResources.ThankYou;
|
||||
Content = table;
|
||||
}
|
||||
|
||||
public class CustomViewCell : ViewCell
|
||||
{
|
||||
public CustomViewCell(string text)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
Text = text,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
|
||||
};
|
||||
|
||||
var layout = new StackLayout
|
||||
{
|
||||
Children = { label },
|
||||
Padding = Helpers.OnPlatform(
|
||||
iOS: new Thickness(15, 20),
|
||||
Android: new Thickness(16, 20),
|
||||
Windows: new Thickness(10, 8)),
|
||||
BackgroundColor = Color.White
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
label.TextColor = Color.Black;
|
||||
}
|
||||
|
||||
View = layout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using System.Linq;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsEditFolderPage : ExtendedContentPage
|
||||
{
|
||||
private readonly string _folderId;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public SettingsEditFolderPage(string folderId)
|
||||
{
|
||||
_folderId = folderId;
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public FormEntryCell NameCell { get; set; }
|
||||
public ExtendedTextCell DeleteCell { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var folder = _folderService.GetByIdAsync(_folderId).GetAwaiter().GetResult();
|
||||
if(folder == null)
|
||||
{
|
||||
// TODO: handle error. navigate back? should never happen...
|
||||
return;
|
||||
}
|
||||
|
||||
NameCell = new FormEntryCell(AppResources.Name);
|
||||
NameCell.Entry.Text = folder.Name.Decrypt();
|
||||
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var mainTable = new ExtendedTableView
|
||||
{
|
||||
Intent = TableIntent.Settings,
|
||||
HasUnevenRows = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
NameCell
|
||||
},
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
DeleteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
mainTable.RowHeight = -1;
|
||||
mainTable.EstimatedRowHeight = 70;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
mainTable.BottomPadding = 50;
|
||||
}
|
||||
|
||||
var saveToolBarItem = new ToolbarItem(AppResources.Save, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastAction = DateTime.UtcNow;
|
||||
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(NameCell.Entry.Text))
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
|
||||
AppResources.Name), AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
folder.Name = NameCell.Entry.Text.Encrypt();
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
var saveResult = await _folderService.SaveAsync(folder);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(saveResult.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderUpdated);
|
||||
_googleAnalyticsService.TrackAppEvent("EditedFolder");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveResult.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveResult.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
|
||||
Title = AppResources.EditFolder;
|
||||
Content = mainTable;
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
NameCell.InitEvents();
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
NameCell.Dispose();
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Validate the delete operation. ex. Cannot delete a folder that has ciphers in it?
|
||||
|
||||
var confirmed = await DisplayAlert(null, AppResources.DoYouReallyWantToDelete, AppResources.Yes, AppResources.No);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
|
||||
var deleteTask = await _folderService.DeleteAsync(_folderId);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if(deleteTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.FolderDeleted);
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(deleteTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, deleteTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsHelpPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public SettingsHelpPage()
|
||||
{
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedTextCell EmailCell { get; set; }
|
||||
public ExtendedTextCell WebsiteCell { get; set; }
|
||||
public ExtendedTextCell BugCell { get; set; }
|
||||
public RedrawableStackLayout StackLayout { get; set; }
|
||||
private CustomLabel EmailLabel { get; set; }
|
||||
private CustomLabel WebsiteLabel { get; set; }
|
||||
private CustomLabel BugLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
EmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.EmailUs,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var emailTable = new CustomTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
EmailCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EmailLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.EmailUsDescription
|
||||
};
|
||||
|
||||
WebsiteCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.VisitOurWebsite,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var websiteTable = new CustomTableView(this)
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
WebsiteCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebsiteLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.VisitOurWebsiteDescription
|
||||
};
|
||||
|
||||
BugCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.FileBugReport,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var bugTable = new CustomTableView(this)
|
||||
{
|
||||
NoHeader = true,
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
BugCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BugLabel = new CustomLabel(this)
|
||||
{
|
||||
Text = AppResources.FileBugReportDescription
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children = { emailTable, EmailLabel, websiteTable, WebsiteLabel, bugTable, BugLabel },
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.HelpAndFeedback;
|
||||
Content = new ScrollView { Content = StackLayout };
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
EmailCell.Tapped += EmailCell_Tapped;
|
||||
WebsiteCell.Tapped += WebsiteCell_Tapped;
|
||||
BugCell.Tapped += BugCell_Tapped;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
EmailCell.Tapped -= EmailCell_Tapped;
|
||||
WebsiteCell.Tapped -= WebsiteCell_Tapped;
|
||||
BugCell.Tapped -= BugCell_Tapped;
|
||||
}
|
||||
|
||||
private void EmailCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpEmail");
|
||||
Device.OpenUri(new Uri("mailto:hello@bitwarden.com"));
|
||||
}
|
||||
|
||||
private void WebsiteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpWebsite");
|
||||
Device.OpenUri(new Uri("https://bitwarden.com/contact/"));
|
||||
}
|
||||
|
||||
private void BugCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("HelpBug");
|
||||
Device.OpenUri(new Uri("https://github.com/bitwarden/mobile"));
|
||||
}
|
||||
|
||||
private class CustomTableView : ExtendedTableView
|
||||
{
|
||||
public CustomTableView(SettingsHelpPage page)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RowHeight = -1;
|
||||
EstimatedRowHeight = 44;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomLabel : Label
|
||||
{
|
||||
public CustomLabel(ContentPage page)
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap;
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
Style = (Style)Application.Current.Resources["text-muted"];
|
||||
Margin = new Thickness(15, (page.IsLandscape() ? 5 : 0), 15, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsListFoldersPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IFolderService _folderService;
|
||||
|
||||
public SettingsListFoldersPage()
|
||||
{
|
||||
_folderService = Resolver.Resolve<IFolderService>();
|
||||
Init();
|
||||
}
|
||||
|
||||
public ExtendedObservableCollection<SettingsFolderPageModel> Folders { get; private set; }
|
||||
= new ExtendedObservableCollection<SettingsFolderPageModel>();
|
||||
public ExtendedListView ListView { get; set; }
|
||||
private AddFolderToolBarItem AddItem { get; set; }
|
||||
public Fab Fab { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ListView = new ExtendedListView
|
||||
{
|
||||
ItemsSource = Folders,
|
||||
ItemTemplate = new DataTemplate(() => new SettingsFolderListViewCell(this))
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
var fabLayout = new FabLayout(ListView);
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Fab = new Fab(fabLayout, "plus.png", async (sender, args) =>
|
||||
{
|
||||
await Navigation.PushForDeviceAsync(new SettingsAddFolderPage());
|
||||
});
|
||||
ListView.BottomPadding = 50;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddItem = new AddFolderToolBarItem(this);
|
||||
ToolbarItems.Add(AddItem);
|
||||
}
|
||||
|
||||
Title = AppResources.Folders;
|
||||
Content = fabLayout;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
ListView.ItemSelected += FolderSelected;
|
||||
AddItem?.InitEvents();
|
||||
LoadFoldersAsync().Wait();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
ListView.ItemSelected -= FolderSelected;
|
||||
AddItem?.Dispose();
|
||||
}
|
||||
|
||||
private async Task LoadFoldersAsync()
|
||||
{
|
||||
var folders = await _folderService.GetAllAsync();
|
||||
var pageFolders = folders.Select(f => new SettingsFolderPageModel(f)).OrderBy(f => f.Name);
|
||||
Folders.ResetWithRange(pageFolders);
|
||||
}
|
||||
|
||||
private async void FolderSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
var folder = e.SelectedItem as SettingsFolderPageModel;
|
||||
var page = new SettingsEditFolderPage(folder.Id);
|
||||
await Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
|
||||
private class AddFolderToolBarItem : ExtendedToolbarItem
|
||||
{
|
||||
private readonly SettingsListFoldersPage _page;
|
||||
|
||||
public AddFolderToolBarItem(SettingsListFoldersPage page)
|
||||
{
|
||||
_page = page;
|
||||
Text = AppResources.Add;
|
||||
Icon = "plus.png";
|
||||
ClickAction = () => ClickedItem();
|
||||
}
|
||||
|
||||
private async void ClickedItem()
|
||||
{
|
||||
var page = new SettingsAddFolderPage();
|
||||
await _page.Navigation.PushForDeviceAsync(page);
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsFolderListViewCell : ExtendedTextCell
|
||||
{
|
||||
public SettingsFolderListViewCell(SettingsListFoldersPage page)
|
||||
{
|
||||
this.SetBinding(TextProperty, nameof(SettingsFolderPageModel.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsOptionsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
|
||||
public SettingsOptionsPage()
|
||||
{
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private RedrawableStackLayout StackLayout { get; set; }
|
||||
private ExtendedSwitchCell CopyTotpCell { get; set; }
|
||||
private Label CopyTotpLabel { get; set; }
|
||||
private ExtendedSwitchCell WebsiteIconsCell { get; set; }
|
||||
private Label WebsiteIconsLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillPersistNotificationCell { get; set; }
|
||||
private Label AutofillPersistNotificationLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillPasswordFieldCell { get; set; }
|
||||
private Label AutofillPasswordFieldLabel { get; set; }
|
||||
private ExtendedSwitchCell AutofillAlwaysCell { get; set; }
|
||||
private Label AutofillAlwaysLabel { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
WebsiteIconsCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.DisableWebsiteIcons,
|
||||
On = _appSettings.DisableWebsiteIcons
|
||||
};
|
||||
|
||||
var websiteIconsTable = new FormTableView(this, true)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
WebsiteIconsCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CopyTotpCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.DisableAutoTotpCopy,
|
||||
On = _settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false)
|
||||
};
|
||||
|
||||
var totpTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
CopyTotpCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CopyTotpLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.DisableAutoTotpCopyDescription
|
||||
};
|
||||
|
||||
WebsiteIconsLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.DisableWebsiteIconsDescription
|
||||
};
|
||||
|
||||
StackLayout = new RedrawableStackLayout
|
||||
{
|
||||
Children =
|
||||
{
|
||||
websiteIconsTable, WebsiteIconsLabel,
|
||||
totpTable, CopyTotpLabel
|
||||
},
|
||||
Spacing = 0
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillAlways,
|
||||
On = !_appSettings.AutofillPersistNotification && !_appSettings.AutofillPasswordField
|
||||
};
|
||||
|
||||
var autofillAlwaysTable = new FormTableView(this, true)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(AppResources.AutofillAccessibilityService)
|
||||
{
|
||||
AutofillAlwaysCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillAlwaysLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillAlwaysDescription
|
||||
};
|
||||
|
||||
AutofillPersistNotificationCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillPersistNotification,
|
||||
On = _appSettings.AutofillPersistNotification
|
||||
};
|
||||
|
||||
var autofillPersistNotificationTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
AutofillPersistNotificationCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillPersistNotificationLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillPersistNotificationDescription
|
||||
};
|
||||
|
||||
AutofillPasswordFieldCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.AutofillPasswordField,
|
||||
On = _appSettings.AutofillPasswordField
|
||||
};
|
||||
|
||||
var autofillPasswordFieldTable = new FormTableView(this)
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
AutofillPasswordFieldCell
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutofillPasswordFieldLabel = new FormTableLabel(this)
|
||||
{
|
||||
Text = AppResources.AutofillPasswordFieldDescription
|
||||
};
|
||||
|
||||
StackLayout.Children.Add(autofillAlwaysTable);
|
||||
StackLayout.Children.Add(AutofillAlwaysLabel);
|
||||
StackLayout.Children.Add(autofillPasswordFieldTable);
|
||||
StackLayout.Children.Add(AutofillPasswordFieldLabel);
|
||||
StackLayout.Children.Add(autofillPersistNotificationTable);
|
||||
StackLayout.Children.Add(AutofillPersistNotificationLabel);
|
||||
}
|
||||
|
||||
var scrollView = new ScrollView
|
||||
{
|
||||
Content = StackLayout
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.Options;
|
||||
Content = scrollView;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
WebsiteIconsCell.OnChanged += WebsiteIconsCell_Changed;
|
||||
CopyTotpCell.OnChanged += CopyTotpCell_OnChanged;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell.OnChanged += AutofillAlwaysCell_OnChanged;
|
||||
AutofillPasswordFieldCell.OnChanged += AutofillPasswordFieldCell_OnChanged;
|
||||
AutofillPersistNotificationCell.OnChanged += AutofillPersistNotificationCell_OnChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
WebsiteIconsCell.OnChanged -= WebsiteIconsCell_Changed;
|
||||
CopyTotpCell.OnChanged -= CopyTotpCell_OnChanged;
|
||||
|
||||
if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
AutofillAlwaysCell.OnChanged -= AutofillAlwaysCell_OnChanged;
|
||||
AutofillPasswordFieldCell.OnChanged -= AutofillPasswordFieldCell_OnChanged;
|
||||
AutofillPersistNotificationCell.OnChanged -= AutofillPersistNotificationCell_OnChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void WebsiteIconsCell_Changed(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.DisableWebsiteIcons = cell.On;
|
||||
}
|
||||
|
||||
private void CopyTotpCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingDisableTotpCopy, cell.On);
|
||||
}
|
||||
|
||||
private void AutofillAlwaysCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPasswordFieldCell.On = false;
|
||||
AutofillPersistNotificationCell.On = false;
|
||||
_appSettings.AutofillPersistNotification = false;
|
||||
_appSettings.AutofillPasswordField = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutofillPersistNotificationCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.AutofillPersistNotification = cell.On;
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPasswordFieldCell.On = false;
|
||||
AutofillAlwaysCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutofillPasswordFieldCell_OnChanged(object sender, ToggledEventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appSettings.AutofillPasswordField = cell.On;
|
||||
if(cell.On)
|
||||
{
|
||||
AutofillPersistNotificationCell.On = false;
|
||||
AutofillAlwaysCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class FormTableView : ExtendedTableView
|
||||
{
|
||||
public FormTableView(SettingsOptionsPage page, bool header = false)
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
EnableScrolling = false;
|
||||
HasUnevenRows = true;
|
||||
EnableSelection = true;
|
||||
VerticalOptions = LayoutOptions.Start;
|
||||
NoFooter = true;
|
||||
NoHeader = !header;
|
||||
WrappingStackLayout = () => page.StackLayout;
|
||||
}
|
||||
}
|
||||
|
||||
private class FormTableLabel : Label
|
||||
{
|
||||
public FormTableLabel(Page page)
|
||||
{
|
||||
LineBreakMode = LineBreakMode.WordWrap;
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
Style = (Style)Application.Current.Resources["text-muted"];
|
||||
Margin = new Thickness(15, (page.IsLandscape() ? 5 : 0), 15, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,552 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Controls;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsPage : ExtendedContentPage
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IFingerprint _fingerprint;
|
||||
private readonly IPushNotificationService _pushNotification;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly MainPage _mainPage;
|
||||
|
||||
// TODO: Model binding context?
|
||||
|
||||
public SettingsPage(MainPage mainPage)
|
||||
{
|
||||
_mainPage = mainPage;
|
||||
_authService = Resolver.Resolve<IAuthService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_fingerprint = Resolver.Resolve<IFingerprint>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotificationService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_lockService = Resolver.Resolve<ILockService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private ExtendedSwitchCell PinCell { get; set; }
|
||||
private ExtendedSwitchCell FingerprintCell { get; set; }
|
||||
private ExtendedTextCell LockOptionsCell { get; set; }
|
||||
private ExtendedTextCell TwoStepCell { get; set; }
|
||||
private ExtendedTextCell ChangeMasterPasswordCell { get; set; }
|
||||
private ExtendedTextCell ChangeEmailCell { get; set; }
|
||||
private ExtendedTextCell FoldersCell { get; set; }
|
||||
private ExtendedTextCell SyncCell { get; set; }
|
||||
private ExtendedTextCell LockCell { get; set; }
|
||||
private ExtendedTextCell LogOutCell { get; set; }
|
||||
private ExtendedTextCell AboutCell { get; set; }
|
||||
private ExtendedTextCell HelpCell { get; set; }
|
||||
private ExtendedTextCell RateCell { get; set; }
|
||||
private ExtendedTextCell OptionsCell { get; set; }
|
||||
private LongDetailViewCell RateCellLong { get; set; }
|
||||
private ExtendedTableView Table { get; set; }
|
||||
|
||||
private async void Init()
|
||||
{
|
||||
PinCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = AppResources.UnlockWithPIN,
|
||||
On = _settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false)
|
||||
};
|
||||
|
||||
LockOptionsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.LockOptions,
|
||||
Detail = GetLockOptionsDetailsText(),
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
TwoStepCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.TwoStepLogin,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
LockCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Lock
|
||||
};
|
||||
|
||||
var securitySecion = new TableSection(AppResources.Security)
|
||||
{
|
||||
LockOptionsCell,
|
||||
PinCell,
|
||||
LockCell,
|
||||
TwoStepCell
|
||||
};
|
||||
|
||||
if((await _fingerprint.GetAvailabilityAsync()) == FingerprintAvailability.Available)
|
||||
{
|
||||
var fingerprintName = Helpers.OnPlatform(
|
||||
iOS: _deviceInfoService.HasFaceIdSupport ? AppResources.FaceID : AppResources.TouchID,
|
||||
Android: AppResources.Fingerprint,
|
||||
Windows: AppResources.WindowsHello);
|
||||
FingerprintCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = string.Format(AppResources.UnlockWith, fingerprintName),
|
||||
On = _settings.GetValueOrDefault(Constants.SettingFingerprintUnlockOn, false),
|
||||
IsEnabled = true
|
||||
};
|
||||
securitySecion.Insert(1, FingerprintCell);
|
||||
}
|
||||
|
||||
ChangeMasterPasswordCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeMasterPassword,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
ChangeEmailCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.ChangeEmail,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
FoldersCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Folders,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
SyncCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Sync,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
LogOutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.LogOut
|
||||
};
|
||||
|
||||
AboutCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.About,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
HelpCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.HelpAndFeedback,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
OptionsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.Options,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
var otherSection = new TableSection(AppResources.Other)
|
||||
{
|
||||
OptionsCell,
|
||||
AboutCell,
|
||||
HelpCell
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RateCellLong = new LongDetailViewCell(AppResources.RateTheApp, AppResources.RateTheAppDescriptionAppStore);
|
||||
otherSection.Add(RateCellLong);
|
||||
}
|
||||
else
|
||||
{
|
||||
RateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.RateTheApp,
|
||||
Detail = AppResources.RateTheAppDescription,
|
||||
ShowDisclousure = true,
|
||||
DetailLineBreakMode = LineBreakMode.WordWrap
|
||||
};
|
||||
otherSection.Add(RateCell);
|
||||
}
|
||||
|
||||
Table = new CustomTable
|
||||
{
|
||||
Root = new TableRoot
|
||||
{
|
||||
securitySecion,
|
||||
new TableSection(AppResources.Account)
|
||||
{
|
||||
ChangeMasterPasswordCell,
|
||||
ChangeEmailCell,
|
||||
LogOutCell
|
||||
},
|
||||
new TableSection(AppResources.Manage)
|
||||
{
|
||||
FoldersCell,
|
||||
SyncCell
|
||||
},
|
||||
otherSection
|
||||
}
|
||||
};
|
||||
|
||||
Title = AppResources.Settings;
|
||||
Content = Table;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
PinCell.OnChanged += PinCell_Changed;
|
||||
LockOptionsCell.Tapped += LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped += TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped += ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged += FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped += ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped += FoldersCell_Tapped;
|
||||
SyncCell.Tapped += SyncCell_Tapped;
|
||||
LockCell.Tapped += LockCell_Tapped;
|
||||
LogOutCell.Tapped += LogOutCell_Tapped;
|
||||
AboutCell.Tapped += AboutCell_Tapped;
|
||||
HelpCell.Tapped += HelpCell_Tapped;
|
||||
OptionsCell.Tapped += OptionsCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped += RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped += RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
PinCell.OnChanged -= PinCell_Changed;
|
||||
LockOptionsCell.Tapped -= LockOptionsCell_Tapped;
|
||||
TwoStepCell.Tapped -= TwoStepCell_Tapped;
|
||||
ChangeMasterPasswordCell.Tapped -= ChangeMasterPasswordCell_Tapped;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.OnChanged -= FingerprintCell_Changed;
|
||||
}
|
||||
|
||||
ChangeEmailCell.Tapped -= ChangeEmailCell_Tapped;
|
||||
FoldersCell.Tapped -= FoldersCell_Tapped;
|
||||
SyncCell.Tapped -= SyncCell_Tapped;
|
||||
LockCell.Tapped -= LockCell_Tapped;
|
||||
LogOutCell.Tapped -= LogOutCell_Tapped;
|
||||
AboutCell.Tapped -= AboutCell_Tapped;
|
||||
HelpCell.Tapped -= HelpCell_Tapped;
|
||||
OptionsCell.Tapped -= OptionsCell_Tapped;
|
||||
|
||||
if(RateCellLong != null)
|
||||
{
|
||||
RateCellLong.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
|
||||
if(RateCell != null)
|
||||
{
|
||||
RateCell.Tapped -= RateCell_Tapped;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.Android && _mainPage != null)
|
||||
{
|
||||
_mainPage.ResetToVaultPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButtonPressed();
|
||||
}
|
||||
|
||||
private async void TwoStepCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.TwoStepLoginConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "TwoStep");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/setup-two-step-login/"));
|
||||
}
|
||||
|
||||
private async void LockOptionsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var selection = await DisplayActionSheet(AppResources.LockOptions, AppResources.Cancel, null,
|
||||
AppResources.LockOptionImmediately, AppResources.LockOption1Minute, AppResources.LockOption15Minutes,
|
||||
AppResources.LockOption1Hour, AppResources.LockOption4Hours, AppResources.Never);
|
||||
|
||||
if(selection == null || selection == AppResources.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(selection == AppResources.LockOptionImmediately)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 0);
|
||||
}
|
||||
else if(selection == AppResources.LockOption1Minute)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60);
|
||||
}
|
||||
else if(selection == AppResources.LockOption15Minutes)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 15);
|
||||
}
|
||||
else if(selection == AppResources.LockOption1Hour)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 60);
|
||||
}
|
||||
else if(selection == AppResources.LockOption4Hours)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, 60 * 60 * 4);
|
||||
}
|
||||
else if(selection == AppResources.Never)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingLockSeconds, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LockOptionsCell.Detail = selection;
|
||||
}
|
||||
|
||||
private void SyncCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsSyncPage()));
|
||||
}
|
||||
|
||||
private void AboutCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsAboutPage()));
|
||||
}
|
||||
|
||||
private void RateCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "RateApp");
|
||||
_deviceActionService.RateApp();
|
||||
}
|
||||
|
||||
private void HelpCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsHelpPage()));
|
||||
}
|
||||
|
||||
private void LockCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
_googleAnalyticsService.TrackAppEvent("Locked");
|
||||
Device.BeginInvokeOnMainThread(async () => await _lockService.CheckLockAsync(true));
|
||||
}
|
||||
|
||||
private async void LogOutCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.LogoutConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_authService.LogOut();
|
||||
}
|
||||
|
||||
private async void ChangeMasterPasswordCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.ChangePasswordConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "ChangePassword");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/change-your-master-password/"));
|
||||
}
|
||||
|
||||
private async void ChangeEmailCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var confirmed = await DisplayAlert(null, AppResources.ChangeEmailConfirmation, AppResources.Yes,
|
||||
AppResources.Cancel);
|
||||
if(!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_googleAnalyticsService.TrackAppEvent("OpenedSetting", "ChangeEmail");
|
||||
Device.OpenUri(new Uri("https://help.bitwarden.com/article/change-your-email/"));
|
||||
}
|
||||
|
||||
private void FingerprintCell_Changed(object sender, EventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingFingerprintUnlockOn, cell.On);
|
||||
|
||||
if(cell.On)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, false);
|
||||
PinCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PinCell_Changed(object sender, EventArgs e)
|
||||
{
|
||||
var cell = sender as ExtendedSwitchCell;
|
||||
if(cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(cell.On && !_settings.GetValueOrDefault(Constants.SettingPinUnlockOn, false))
|
||||
{
|
||||
cell.On = false;
|
||||
var pinPage = new SettingsPinPage((page) => PinEntered(page));
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(pinPage));
|
||||
}
|
||||
else if(!cell.On)
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PinEntered(SettingsPinPage page)
|
||||
{
|
||||
page.PinControl.Entry.Unfocus();
|
||||
page.Navigation.PopModalAsync();
|
||||
|
||||
_authService.PIN = page.Model.PIN;
|
||||
|
||||
_settings.AddOrUpdateValue(Constants.SettingPinUnlockOn, true);
|
||||
_settings.AddOrUpdateValue(Constants.SettingFingerprintUnlockOn, false);
|
||||
PinCell.On = true;
|
||||
|
||||
if(FingerprintCell != null)
|
||||
{
|
||||
FingerprintCell.On = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OptionsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsOptionsPage()));
|
||||
}
|
||||
|
||||
private void FoldersCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsListFoldersPage()));
|
||||
}
|
||||
|
||||
private string GetLockOptionsDetailsText()
|
||||
{
|
||||
var lockSeconds = _settings.GetValueOrDefault(Constants.SettingLockSeconds, 60 * 15);
|
||||
if(lockSeconds == -1)
|
||||
{
|
||||
return AppResources.Never;
|
||||
}
|
||||
else if(lockSeconds == 60)
|
||||
{
|
||||
return AppResources.LockOption1Minute;
|
||||
}
|
||||
else if(lockSeconds == 60 * 15)
|
||||
{
|
||||
return AppResources.LockOption15Minutes;
|
||||
}
|
||||
else if(lockSeconds == 60 * 60)
|
||||
{
|
||||
return AppResources.LockOption1Hour;
|
||||
}
|
||||
else if(lockSeconds == 60 * 60 * 4)
|
||||
{
|
||||
return AppResources.LockOption4Hours;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppResources.LockOptionImmediately;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomTable : ExtendedTableView
|
||||
{
|
||||
public CustomTable()
|
||||
{
|
||||
Intent = TableIntent.Settings;
|
||||
HasUnevenRows = true;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
RowHeight = -1;
|
||||
EstimatedRowHeight = 44;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LongDetailViewCell : ExtendedViewCell
|
||||
{
|
||||
public LongDetailViewCell(string labelText, string detailText)
|
||||
{
|
||||
Label = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
Text = labelText
|
||||
};
|
||||
|
||||
Detail = new Label
|
||||
{
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
VerticalOptions = LayoutOptions.End,
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
Text = detailText
|
||||
};
|
||||
|
||||
var labelDetailStackLayout = new StackLayout
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.StartAndExpand,
|
||||
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||
Children = { Label, Detail },
|
||||
Padding = new Thickness(15)
|
||||
};
|
||||
|
||||
ShowDisclousure = true;
|
||||
View = labelDetailStackLayout;
|
||||
}
|
||||
|
||||
public Label Label { get; set; }
|
||||
public Label Detail { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Resources;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Controls;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsPinPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private Action<SettingsPinPage> _pinEnteredAction;
|
||||
|
||||
public SettingsPinPage(Action<SettingsPinPage> pinEnteredAction)
|
||||
{
|
||||
_pinEnteredAction = pinEnteredAction;
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public PinPageModel Model { get; set; } = new PinPageModel();
|
||||
public PinControl PinControl { get; set; }
|
||||
public TapGestureRecognizer Tgr { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var instructionLabel = new Label
|
||||
{
|
||||
Text = AppResources.SetPINDirection,
|
||||
LineBreakMode = LineBreakMode.WordWrap,
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center,
|
||||
Style = (Style)Application.Current.Resources["text-muted"]
|
||||
};
|
||||
|
||||
PinControl = new PinControl();
|
||||
PinControl.Label.SetBinding(Label.TextProperty, nameof(PinPageModel.LabelText));
|
||||
PinControl.Entry.SetBinding(Entry.TextProperty, nameof(PinPageModel.PIN));
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(30, 40),
|
||||
Spacing = 20,
|
||||
Children = { PinControl.Label, instructionLabel, PinControl.Entry }
|
||||
};
|
||||
|
||||
Tgr = new TapGestureRecognizer();
|
||||
PinControl.Label.GestureRecognizers.Add(Tgr);
|
||||
instructionLabel.GestureRecognizers.Add(Tgr);
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
|
||||
}
|
||||
|
||||
Title = AppResources.SetPIN;
|
||||
Content = stackLayout;
|
||||
Content.GestureRecognizers.Add(Tgr);
|
||||
BindingContext = Model;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Tgr.Tapped += Tgr_Tapped;
|
||||
PinControl.OnPinEntered += PinEntered;
|
||||
PinControl.InitEvents();
|
||||
PinControl.Entry.FocusWithDelay();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
PinControl.Dispose();
|
||||
Tgr.Tapped -= Tgr_Tapped;
|
||||
PinControl.OnPinEntered -= PinEntered;
|
||||
}
|
||||
|
||||
protected void PinEntered(object sender, EventArgs args)
|
||||
{
|
||||
_pinEnteredAction?.Invoke(this);
|
||||
}
|
||||
|
||||
private void Tgr_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
PinControl.Entry.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SettingsSyncPage : ExtendedContentPage
|
||||
{
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly ISettings _settings;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
|
||||
public SettingsSyncPage()
|
||||
{
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public Label LastSyncLabel { get; set; }
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var syncButton = new ExtendedButton
|
||||
{
|
||||
Text = AppResources.SyncVaultNow,
|
||||
Command = new Command(async () => await SyncAsync()),
|
||||
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
||||
};
|
||||
|
||||
LastSyncLabel = new Label
|
||||
{
|
||||
Style = (Style)Application.Current.Resources["text-muted"],
|
||||
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
||||
HorizontalTextAlignment = TextAlignment.Center
|
||||
};
|
||||
|
||||
SetLastSync();
|
||||
|
||||
var stackLayout = new StackLayout
|
||||
{
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
Children = { syncButton, LastSyncLabel },
|
||||
Padding = new Thickness(15, 0)
|
||||
};
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.UWP)
|
||||
{
|
||||
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
|
||||
}
|
||||
|
||||
Title = AppResources.Sync;
|
||||
Content = stackLayout;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
private void SetLastSync()
|
||||
{
|
||||
DateTime? lastSyncDate = null;
|
||||
if(_settings.Contains(Constants.LastSync))
|
||||
{
|
||||
lastSyncDate = _settings.GetValueOrDefault(Constants.LastSync, DateTime.UtcNow);
|
||||
}
|
||||
try
|
||||
{
|
||||
LastSyncLabel.Text = AppResources.LastSync + " " + lastSyncDate?.ToLocalTime().ToString() ?? AppResources.Never;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// some users with different calendars have issues with ToString()ing a date
|
||||
// it seems the linker is at fault. just catch for now since this isn't that important.
|
||||
// ref http://bit.ly/2c2JU7b
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SyncAsync()
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
{
|
||||
AlertNoConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
|
||||
var succeeded = await _syncService.FullSyncAsync(true);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if(succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.SyncingComplete);
|
||||
_googleAnalyticsService.TrackAppEvent("Synced");
|
||||
}
|
||||
else
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.SyncingFailed);
|
||||
}
|
||||
|
||||
SetLastSync();
|
||||
}
|
||||
|
||||
public void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user