From 84a82f0876c03da87079f3a57fa8b367b79bc6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Bispo?= Date: Wed, 17 May 2023 17:46:45 +0100 Subject: [PATCH 01/85] [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. --- .../Accounts/LoginApproveDevicePage.xaml | 54 +++++++++++ .../Accounts/LoginApproveDevicePage.xaml.cs | 24 +++++ .../Accounts/LoginApproveDeviceViewModel.cs | 68 ++++++++++++++ src/App/Resources/AppResources.Designer.cs | 90 +++++++++++++++---- src/App/Resources/AppResources.resx | 18 ++++ 5 files changed, 236 insertions(+), 18 deletions(-) create mode 100644 src/App/Pages/Accounts/LoginApproveDevicePage.xaml create mode 100644 src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs create mode 100644 src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs diff --git a/src/App/Pages/Accounts/LoginApproveDevicePage.xaml b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml new file mode 100644 index 00000000000..452f998e9be --- /dev/null +++ b/src/App/Pages/Accounts/LoginApproveDevicePage.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Settings/OtherSettingsPage.xaml.cs b/src/App/Pages/Settings/OtherSettingsPage.xaml.cs new file mode 100644 index 00000000000..58e10b47257 --- /dev/null +++ b/src/App/Pages/Settings/OtherSettingsPage.xaml.cs @@ -0,0 +1,44 @@ +using System; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Services; +using Bit.Core.Utilities; + +namespace Bit.App.Pages +{ + public partial class OtherSettingsPage : BaseModalContentPage + { + private OtherSettingsPageViewModel _vm; + + public OtherSettingsPage() + { + InitializeComponent(); + _vm = BindingContext as OtherSettingsPageViewModel; + _vm.Page = this; + } + + protected async override void OnAppearing() + { + base.OnAppearing(); + + try + { + _vm.SubscribeEvents(); + await _vm.InitAsync(); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + ServiceContainer.Resolve().ShowToast(null, null, AppResources.AnErrorHasOccurred); + + Navigation.PopModalAsync().FireAndForget(); + } + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + _vm.UnsubscribeEvents(); + } + } +} diff --git a/src/App/Pages/Settings/OtherSettingsPageViewModel.cs b/src/App/Pages/Settings/OtherSettingsPageViewModel.cs new file mode 100644 index 00000000000..111b2356b92 --- /dev/null +++ b/src/App/Pages/Settings/OtherSettingsPageViewModel.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Input; +using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public class OtherSettingsPageViewModel : BaseViewModel + { + private const int CLEAR_CLIPBOARD_NEVER_OPTION = -1; + + private readonly IDeviceActionService _deviceActionService; + private readonly IPlatformUtilsService _platformUtilsService; + private readonly IStateService _stateService; + private readonly ISyncService _syncService; + private readonly ILocalizeService _localizeService; + private readonly ILogger _logger; + + private string _lastSyncDisplay = "--"; + private bool _inited; + private bool _syncOnRefresh; + private bool _isScreenCaptureAllowed; + + public OtherSettingsPageViewModel() + { + _deviceActionService = ServiceContainer.Resolve(); + _platformUtilsService = ServiceContainer.Resolve(); + _stateService = ServiceContainer.Resolve(); + _syncService = ServiceContainer.Resolve(); + _localizeService = ServiceContainer.Resolve(); + _logger = ServiceContainer.Resolve(); + + SyncCommand = CreateDefaultAsyncCommnad(SyncAsync, _ => _inited); + ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncCommnad(ToggleIsScreenCaptureAllowedAsync, _ => _inited); + + ClearClipboardPickerViewModel = new PickerViewModel( + _deviceActionService, + OnClearClipboardChangedAsync, + AppResources.ClearClipboard, + _ => _inited, + ex => HandleException(ex)); + } + + public bool EnableSyncOnRefresh + { + get => _syncOnRefresh; + set + { + if (SetProperty(ref _syncOnRefresh, value)) + { + UpdateSyncOnRefreshAsync().FireAndForget(); + } + } + } + + public string LastSyncDisplay + { + get => $"{AppResources.LastSync} {_lastSyncDisplay}"; + set => SetProperty(ref _lastSyncDisplay, value); + } + + public PickerViewModel ClearClipboardPickerViewModel { get; } + + public bool IsScreenCaptureAllowed + { + get => _isScreenCaptureAllowed; + set + { + if (SetProperty(ref _isScreenCaptureAllowed, value)) + { + ((ICommand)ToggleIsScreenCaptureAllowedCommand).Execute(null); + } + } + } + + public bool CanToggleeScreenCaptureAllowed => ToggleIsScreenCaptureAllowedCommand.CanExecute(null); + + public AsyncCommand SyncCommand { get; } + public AsyncCommand ToggleIsScreenCaptureAllowedCommand { get; } + + public async Task InitAsync() + { + await SetLastSyncAsync(); + + EnableSyncOnRefresh = await _stateService.GetSyncOnRefreshAsync(); + + await InitClearClipboardAsync(); + + _inited = true; + + MainThread.BeginInvokeOnMainThread(() => + { + SyncCommand.RaiseCanExecuteChanged(); + ClearClipboardPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleIsScreenCaptureAllowedCommand.RaiseCanExecuteChanged(); + }); + } + + private async Task InitClearClipboardAsync() + { + var clearClipboardOptions = new Dictionary + { + [CLEAR_CLIPBOARD_NEVER_OPTION] = AppResources.Never, + [10] = AppResources.TenSeconds, + [20] = AppResources.TwentySeconds, + [30] = AppResources.ThirtySeconds, + [60] = AppResources.OneMinute + }; + if (Device.RuntimePlatform != Device.iOS) + { + clearClipboardOptions.Add(120, AppResources.TwoMinutes); + clearClipboardOptions.Add(300, AppResources.FiveMinutes); + } + + var clearClipboard = await _stateService.GetClearClipboardAsync() ?? CLEAR_CLIPBOARD_NEVER_OPTION; + if (!clearClipboardOptions.ContainsKey(clearClipboard)) + { + _logger.Error("There is no clear clipboard options for key: " + clearClipboard); + clearClipboard = CLEAR_CLIPBOARD_NEVER_OPTION; + } + + ClearClipboardPickerViewModel.Init(clearClipboardOptions, clearClipboard, CLEAR_CLIPBOARD_NEVER_OPTION); + } + + public async Task UpdateSyncOnRefreshAsync() + { + if (_inited) + { + await _stateService.SetSyncOnRefreshAsync(_syncOnRefresh); + } + } + + public async Task SetLastSyncAsync() + { + var last = await _syncService.GetLastSyncAsync(); + if (last is null) + { + LastSyncDisplay = AppResources.Never; + return; + } + + var localDate = last.Value.ToLocalTime(); + LastSyncDisplay = string.Format("{0} {1}", + _localizeService.GetLocaleShortDate(localDate), + _localizeService.GetLocaleShortTime(localDate)); + } + + public async Task SyncAsync() + { + if (Connectivity.NetworkAccess == NetworkAccess.None) + { + await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, + AppResources.InternetConnectionRequiredTitle); + return; + } + + await _deviceActionService.ShowLoadingAsync(AppResources.Syncing); + await _syncService.SyncPasswordlessLoginRequestsAsync(); + var success = await _syncService.FullSyncAsync(true); + await _deviceActionService.HideLoadingAsync(); + if (!success) + { + await Page.DisplayAlert(null, AppResources.SyncingFailed, AppResources.Ok); + return; + } + + await SetLastSyncAsync(); + _platformUtilsService.ShowToast("success", null, AppResources.SyncingComplete); + } + + private async Task OnClearClipboardChangedAsync(int optionKey) + { + await _stateService.SetClearClipboardAsync(optionKey == CLEAR_CLIPBOARD_NEVER_OPTION ? (int?)null : optionKey); + return true; + } + + private async Task ToggleIsScreenCaptureAllowedAsync() + { + if (IsScreenCaptureAllowed + && + !await Page.DisplayAlert(AppResources.AllowScreenCapture, AppResources.AreYouSureYouWantToEnableScreenCapture, AppResources.Yes, AppResources.No)) + { + _isScreenCaptureAllowed = !IsScreenCaptureAllowed; + TriggerPropertyChanged(nameof(IsScreenCaptureAllowed)); + return; + } + + await _stateService.SetScreenCaptureAllowedAsync(IsScreenCaptureAllowed); + await _deviceActionService.SetScreenCaptureAllowedAsync(); + } + + private void ToggleIsScreenCaptureAllowedCommand_CanExecuteChanged(object sender, EventArgs e) + { + TriggerPropertyChanged(nameof(CanToggleeScreenCaptureAllowed)); + } + + internal void SubscribeEvents() + { + ToggleIsScreenCaptureAllowedCommand.CanExecuteChanged += ToggleIsScreenCaptureAllowedCommand_CanExecuteChanged; + } + + internal void UnsubscribeEvents() + { + ToggleIsScreenCaptureAllowedCommand.CanExecuteChanged -= ToggleIsScreenCaptureAllowedCommand_CanExecuteChanged; + } + } +} diff --git a/src/App/Pages/Settings/SecuritySettingsPage.xaml b/src/App/Pages/Settings/SecuritySettingsPage.xaml new file mode 100644 index 00000000000..42fc93961f9 --- /dev/null +++ b/src/App/Pages/Settings/SecuritySettingsPage.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/src/App/Pages/Settings/SecuritySettingsPage.xaml.cs b/src/App/Pages/Settings/SecuritySettingsPage.xaml.cs new file mode 100644 index 00000000000..52cbce19d88 --- /dev/null +++ b/src/App/Pages/Settings/SecuritySettingsPage.xaml.cs @@ -0,0 +1,12 @@ +namespace Bit.App.Pages +{ + public partial class SecuritySettingsPage : BaseModalContentPage + { + public SecuritySettingsPage() + { + InitializeComponent(); + var vm = BindingContext as SecuritySettingsPageViewModel; + vm.Page = this; + } + } +} diff --git a/src/App/Pages/Settings/SecuritySettingsPageViewModel.cs b/src/App/Pages/Settings/SecuritySettingsPageViewModel.cs new file mode 100644 index 00000000000..81c6563b546 --- /dev/null +++ b/src/App/Pages/Settings/SecuritySettingsPageViewModel.cs @@ -0,0 +1,14 @@ +using System; +namespace Bit.App.Pages +{ + /// + /// TODO: Move related things here after TDE release is done. + /// + public class SecuritySettingsPageViewModel : BaseViewModel + { + public SecuritySettingsPageViewModel() + { + } + } +} + diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml index 4976573af62..8e8d2efcea6 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml +++ b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml @@ -6,11 +6,31 @@ xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:u="clr-namespace:Bit.App.Utilities" x:DataType="pages:SettingsPageViewModel" - Title="{Binding PageTitle}"> + Title="{u:I18n Settings}" + x:Name="_page"> + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs index 47a8a22c4f6..b20715790f8 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs @@ -20,6 +20,30 @@ public SettingsPage(TabsPage tabsPage) _vm.Page = this; } + protected override bool OnBackButtonPressed() + { + if (Device.RuntimePlatform == Device.Android && _tabsPage != null) + { + _tabsPage.ResetToVaultPage(); + return true; + } + return base.OnBackButtonPressed(); + } + } + + public partial class SettingsPage2 : BaseContentPage + { + private readonly TabsPage _tabsPage; + private SettingsPageViewModel2 _vm; + + public SettingsPage2(TabsPage tabsPage) + { + _tabsPage = tabsPage; + //InitializeComponent(); + _vm = BindingContext as SettingsPageViewModel2; + _vm.Page = this; + } + public async Task InitAsync() { await _vm.InitAsync(); @@ -43,7 +67,7 @@ protected override bool OnBackButtonPressed() void ActivateTimePicker(object sender, EventArgs args) { var stackLayout = (ExtendedStackLayout)sender; - SettingsPageListItem item = (SettingsPageListItem)stackLayout.BindingContext; + SettingsPageListItemOld item = (SettingsPageListItemOld)stackLayout.BindingContext; if (item.ShowTimeInput) { var timePicker = stackLayout.Children.Where(x => x is TimePicker).FirstOrDefault(); @@ -64,7 +88,7 @@ async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs a private void RowSelected(object sender, SelectionChangedEventArgs e) { ((ExtendedCollectionView)sender).SelectedItem = null; - if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item) + if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItemOld item) { _vm?.ExecuteSettingItemCommand.Execute(item); } diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageListGroup.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageListGroup.cs index 319e3e77336..ded46d4ed14 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageListGroup.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageListGroup.cs @@ -2,9 +2,9 @@ namespace Bit.App.Pages { - public class SettingsPageListGroup : List + public class SettingsPageListGroup : List { - public SettingsPageListGroup(List groupItems, string name, bool doUpper = true, + public SettingsPageListGroup(List groupItems, string name, bool doUpper = true, bool first = false) { AddRange(groupItems); diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageListItem.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageListItem.cs index e2b2b029505..d685ca5d965 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageListItem.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageListItem.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Threading.Tasks; using Bit.App.Resources; using Bit.App.Utilities; @@ -8,7 +7,31 @@ namespace Bit.App.Pages { - public class SettingsPageListItem : ISettingsPageListItem + public class SettingsPageListItem + { + private string _nameResourceKey; + + public SettingsPageListItem(string nameResourceKey, Func executeAsync) + { + _nameResourceKey = nameResourceKey; + ExecuteAsync = executeAsync; + } + + public string Name => AppResources.ResourceManager.GetString(_nameResourceKey); + + public Func ExecuteAsync { get; } + + public string AutomationId + { + get + { + return AutomationIdsHelper.AddSuffixFor(AutomationIdsHelper.ToEnglishTitleCase(_nameResourceKey), SuffixType.Cell); + } + } + } + + [Obsolete("Remove when Settings Reorganization is DONE")] + public class SettingsPageListItemOld : ISettingsPageListItem { public string Icon { get; set; } public string Name { get; set; } diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageListItemSelector.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageListItemSelector.cs index 74dae3f2d8a..38c79fe8ead 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageListItemSelector.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageListItemSelector.cs @@ -14,7 +14,7 @@ protected override DataTemplate OnSelectTemplate(object item, BindableObject con { return HeaderTemplate; } - if (item is SettingsPageListItem listItem) + if (item is SettingsPageListItemOld listItem) { return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate; } diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 4019c43c046..09812c4eb05 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -8,7 +8,6 @@ using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; -using Bit.Core.Services; using Bit.Core.Utilities; using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; @@ -16,6 +15,35 @@ namespace Bit.App.Pages { public class SettingsPageViewModel : BaseViewModel + { + public SettingsPageViewModel() + { + ExecuteSettingItemCommand = new AsyncCommand(item => item.ExecuteAsync(), + onException: ex => HandleException(ex), + allowsMultipleExecutions: false); + + SettingsItems = new List + { + new SettingsPageListItem(nameof(AppResources.AccountSecurity), () => NavigateToAsync(new SecuritySettingsPage())), + new SettingsPageListItem(nameof(AppResources.Autofill), () => NavigateToAsync(new AutofillSettingsPage())), + new SettingsPageListItem(nameof(AppResources.Vault), () => NavigateToAsync(new VaultSettingsPage())), + new SettingsPageListItem(nameof(AppResources.Appearance), () => NavigateToAsync(new AppearanceSettingsPage())), + new SettingsPageListItem(nameof(AppResources.Other), () => NavigateToAsync(new OtherSettingsPage())), + new SettingsPageListItem(nameof(AppResources.About), () => NavigateToAsync(new AboutSettingsPage())) + }; + } + + public List SettingsItems { get; } + + public IAsyncCommand ExecuteSettingItemCommand { get; } + + private async Task NavigateToAsync(Page page) + { + await Page.Navigation.PushModalAsync(new NavigationPage(page)); + } + } + + public class SettingsPageViewModel2 : BaseViewModel { private readonly IPlatformUtilsService _platformUtilsService; private readonly ICryptoService _cryptoService; @@ -75,7 +103,7 @@ public class SettingsPageViewModel : BaseViewModel private List> _vaultTimeoutOptions = VaultTimeoutOptions; private List> _vaultTimeoutActionOptions = VaultTimeoutActionOptions; - public SettingsPageViewModel() + public SettingsPageViewModel2() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); @@ -98,14 +126,14 @@ public SettingsPageViewModel() GroupedItems = new ObservableRangeCollection(); PageTitle = AppResources.Settings; - ExecuteSettingItemCommand = new AsyncCommand(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false); + ExecuteSettingItemCommand = new AsyncCommand(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false); } private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin; public ObservableRangeCollection GroupedItems { get; set; } - public IAsyncCommand ExecuteSettingItemCommand { get; } + public IAsyncCommand ExecuteSettingItemCommand { get; } public async Task InitAsync() { @@ -132,7 +160,7 @@ public async Task InitAsync() var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync(); - _pin = pinSet != PinLockType.Disabled; + _pin = pinSet != Core.Services.PinLockType.Disabled; _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync(); var timeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock; if (!IsVaultTimeoutActionLockAllowed && timeoutAction == VaultTimeoutAction.Lock) @@ -516,10 +544,10 @@ public void BuildList() //TODO: Refactor this once navigation is abstracted so that it doesn't depend on Page, e.g. Page.Navigation.PushModalAsync... var doUpper = Device.RuntimePlatform != Device.Android; - var autofillItems = new List(); + var autofillItems = new List(); if (Device.RuntimePlatform == Device.Android) { - autofillItems.Add(new SettingsPageListItem + autofillItems.Add(new SettingsPageListItemOld { Name = AppResources.AutofillServices, SubLabel = _autofillHandler.AutofillServicesEnabled() ? AppResources.On : AppResources.Off, @@ -530,63 +558,63 @@ public void BuildList() { if (_deviceActionService.SystemMajorVersion() >= 12) { - autofillItems.Add(new SettingsPageListItem + autofillItems.Add(new SettingsPageListItemOld { Name = AppResources.PasswordAutofill, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage())) }); } - autofillItems.Add(new SettingsPageListItem + autofillItems.Add(new SettingsPageListItemOld { Name = AppResources.AppExtension, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage())) }); } - var manageItems = new List + var manageItems = new List { - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.Folders, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())) }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.Sync, SubLabel = _lastSyncDate, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new SyncPage())) } }; - var securityItems = new List + var securityItems = new List { - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue, ExecuteAsync = () => VaultTimeoutAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.VaultTimeoutAction, SubLabel = _vaultTimeoutActionDisplayValue, ExecuteAsync = () => VaultTimeoutActionAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.UnlockWithPIN, SubLabel = _pin ? AppResources.On : AppResources.Off, ExecuteAsync = () => UpdatePinAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.ApproveLoginRequests, SubLabel = _approvePasswordlessLoginRequests ? AppResources.On : AppResources.Off, ExecuteAsync = () => ApproveLoginRequestsAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.LockNow, ExecuteAsync = () => LockAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.TwoStepLogin, ExecuteAsync = () => TwoStepAsync() @@ -594,7 +622,7 @@ public void BuildList() }; if (_approvePasswordlessLoginRequests) { - manageItems.Add(new SettingsPageListItem + manageItems.Add(new SettingsPageListItemOld { Name = AppResources.PendingLogInRequests, ExecuteAsync = () => PendingLoginRequestsAsync() @@ -608,7 +636,7 @@ public void BuildList() biometricName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID : AppResources.TouchID; } - var item = new SettingsPageListItem + var item = new SettingsPageListItemOld { Name = string.Format(AppResources.UnlockWith, biometricName), SubLabel = _biometric ? AppResources.On : AppResources.Off, @@ -618,7 +646,7 @@ public void BuildList() } if (_vaultTimeoutDisplayValue == AppResources.Custom) { - securityItems.Insert(1, new SettingsPageListItem + securityItems.Insert(1, new SettingsPageListItemOld { Name = AppResources.Custom, Time = TimeSpan.FromMinutes(Math.Abs((double)_vaultTimeout.GetValueOrDefault())), @@ -650,7 +678,7 @@ public void BuildList() policyMinutes % 60, policyAction == Policy.ACTION_LOCK ? AppResources.Lock : AppResources.LogOut); } - securityItems.Insert(0, new SettingsPageListItem + securityItems.Insert(0, new SettingsPageListItemOld { Name = policyAlert, UseFrame = true, @@ -659,49 +687,49 @@ public void BuildList() } if (Device.RuntimePlatform == Device.Android) { - securityItems.Add(new SettingsPageListItem + securityItems.Add(new SettingsPageListItemOld { Name = AppResources.AllowScreenCapture, SubLabel = _screenCaptureAllowed ? AppResources.On : AppResources.Off, ExecuteAsync = () => SetScreenCaptureAllowedAsync() }); } - var accountItems = new List(); + var accountItems = new List(); if (Device.RuntimePlatform == Device.iOS) { - accountItems.Add(new SettingsPageListItem + accountItems.Add(new SettingsPageListItemOld { Name = AppResources.ConnectToWatch, SubLabel = _shouldConnectToWatch ? AppResources.On : AppResources.Off, ExecuteAsync = () => ToggleWatchConnectionAsync() }); } - accountItems.Add(new SettingsPageListItem + accountItems.Add(new SettingsPageListItemOld { Name = AppResources.FingerprintPhrase, ExecuteAsync = () => FingerprintAsync() }); - accountItems.Add(new SettingsPageListItem + accountItems.Add(new SettingsPageListItemOld { Name = AppResources.LogOut, ExecuteAsync = () => LogOutAsync() }); if (_showChangeMasterPassword) { - accountItems.Insert(0, new SettingsPageListItem + accountItems.Insert(0, new SettingsPageListItemOld { Name = AppResources.ChangeMasterPassword, ExecuteAsync = () => ChangePasswordAsync() }); } - var toolsItems = new List + var toolsItems = new List { - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.ImportItems, ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Import()) }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.ExportVault, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())) @@ -709,49 +737,49 @@ public void BuildList() }; if (IncludeLinksWithSubscriptionInfo()) { - toolsItems.Add(new SettingsPageListItem + toolsItems.Add(new SettingsPageListItemOld { Name = AppResources.LearnOrg, ExecuteAsync = () => ShareAsync() }); - toolsItems.Add(new SettingsPageListItem + toolsItems.Add(new SettingsPageListItemOld { Name = AppResources.WebVault, ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => WebVault()) }); } - var otherItems = new List + var otherItems = new List { - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.Options, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new OptionsPage())) }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.About, ExecuteAsync = () => AboutAsync() }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.HelpAndFeedback, ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Help()) }, #if !FDROID - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.SubmitCrashLogs, SubLabel = _reportLoggingEnabled ? AppResources.On : AppResources.Off, ExecuteAsync = () => LoggerReportingAsync() }, #endif - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.RateTheApp, ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Rate()) }, - new SettingsPageListItem + new SettingsPageListItemOld { Name = AppResources.DeleteAccount, ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage())) diff --git a/src/App/Pages/Settings/VaultSettingsPage.xaml b/src/App/Pages/Settings/VaultSettingsPage.xaml new file mode 100644 index 00000000000..ebc130f1f16 --- /dev/null +++ b/src/App/Pages/Settings/VaultSettingsPage.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Settings/VaultSettingsPage.xaml.cs b/src/App/Pages/Settings/VaultSettingsPage.xaml.cs new file mode 100644 index 00000000000..013f06360fb --- /dev/null +++ b/src/App/Pages/Settings/VaultSettingsPage.xaml.cs @@ -0,0 +1,12 @@ +namespace Bit.App.Pages +{ + public partial class VaultSettingsPage : BaseModalContentPage + { + public VaultSettingsPage() + { + InitializeComponent(); + var vm = BindingContext as VaultSettingsPageViewModel; + vm.Page = this; + } + } +} diff --git a/src/App/Pages/Settings/VaultSettingsPageViewModel.cs b/src/App/Pages/Settings/VaultSettingsPageViewModel.cs new file mode 100644 index 00000000000..a8ffe30e497 --- /dev/null +++ b/src/App/Pages/Settings/VaultSettingsPageViewModel.cs @@ -0,0 +1,35 @@ +using System.Windows.Input; +using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Bit.App.Pages +{ + public class VaultSettingsPageViewModel : BaseViewModel + { + public VaultSettingsPageViewModel() + { + var platformUtilsService = ServiceContainer.Resolve(); + + GoToFoldersCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())), + onException: ex => HandleException(ex), + allowsMultipleExecutions: false); + + GoToExportVaultCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())), + onException: ex => HandleException(ex), + allowsMultipleExecutions: false); + + GoToImportItemsCommand = new AsyncCommand( + () => MainThread.InvokeOnMainThreadAsync(() => platformUtilsService.LaunchUri(ExternalLinksConstants.HELP_IMPORT_DATA)), + onException: ex => HandleException(ex), + allowsMultipleExecutions: false); + } + + public ICommand GoToFoldersCommand { get; } + public ICommand GoToExportVaultCommand { get; } + public ICommand GoToImportItemsCommand { get; } + } +} diff --git a/src/App/Pages/TabsPage.cs b/src/App/Pages/TabsPage.cs index 88c63810103..037d7d3babf 100644 --- a/src/App/Pages/TabsPage.cs +++ b/src/App/Pages/TabsPage.cs @@ -146,10 +146,10 @@ protected async override void OnCurrentPageChanged() { await genPage.InitAsync(); } - else if (navPage.RootPage is SettingsPage settingsPage) - { - await settingsPage.InitAsync(); - } + //else if (navPage.RootPage is SettingsPage settingsPage) + //{ + // //await settingsPage.InitAsync(); + //} } } diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 65d0c606e2a..fa163545be2 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -274,6 +274,15 @@ public static string AccountRemovedSuccessfully { } } + /// + /// Looks up a localized string similar to Account security. + /// + public static string AccountSecurity { + get { + return ResourceManager.GetString("AccountSecurity", resourceCulture); + } + } + /// /// Looks up a localized string similar to Switched to next available account. /// @@ -553,6 +562,15 @@ public static string ApiUrl { } } + /// + /// Looks up a localized string similar to Appearance. + /// + public static string Appearance { + get { + return ResourceManager.GetString("Appearance", resourceCulture); + } + } + /// /// Looks up a localized string similar to App extension. /// @@ -1264,6 +1282,15 @@ public static string BitwardenAutofillServiceStep3 { } } + /// + /// Looks up a localized string similar to Bitwarden Help Center. + /// + public static string BitwardenHelpCenter { + get { + return ResourceManager.GetString("BitwardenHelpCenter", resourceCulture); + } + } + /// /// Looks up a localized string similar to Black. /// @@ -1597,6 +1624,15 @@ public static string ConnectToWatch { } } + /// + /// Looks up a localized string similar to Contact Bitwarden support. + /// + public static string ContactBitwardenSupport { + get { + return ResourceManager.GetString("ContactBitwardenSupport", resourceCulture); + } + } + /// /// Looks up a localized string similar to Continue. /// @@ -1615,6 +1651,15 @@ public static string Copy { } } + /// + /// Looks up a localized string similar to Copy app information. + /// + public static string CopyAppInformation { + get { + return ResourceManager.GetString("CopyAppInformation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Copy application. /// @@ -6254,6 +6299,15 @@ public static string SyncingFailed { } } + /// + /// Looks up a localized string similar to Sync now. + /// + public static string SyncNow { + get { + return ResourceManager.GetString("SyncNow", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sync vault now. /// @@ -6751,7 +6805,7 @@ public static string Unlock { } /// - /// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.. + /// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.. /// public static string UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve { get { @@ -7020,6 +7074,15 @@ public static string ValueHasBeenCopied { } } + /// + /// Looks up a localized string similar to Vault. + /// + public static string Vault { + get { + return ResourceManager.GetString("Vault", resourceCulture); + } + } + /// /// Looks up a localized string similar to Vault: {0}. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index c83e6646a3a..2a95bd4918f 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -2762,4 +2762,25 @@ Do you want to switch to this account? Logging in on + + Vault + + + Appearance + + + Account security + + + Bitwarden Help Center + + + Contact Bitwarden support + + + Copy app information + + + Sync now + diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml index 2dac11bf750..cc6f80cf3e9 100644 --- a/src/App/Styles/Base.xaml +++ b/src/App/Styles/Base.xaml @@ -508,6 +508,7 @@ Value="{DynamicResource MutedColor}" /> + + + + + + + diff --git a/src/App/Styles/ControlTemplates.xaml b/src/App/Styles/ControlTemplates.xaml new file mode 100644 index 00000000000..54e76e22b9a --- /dev/null +++ b/src/App/Styles/ControlTemplates.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/src/App/Styles/ControlTemplates.xaml.cs b/src/App/Styles/ControlTemplates.xaml.cs new file mode 100644 index 00000000000..db09fc2e92b --- /dev/null +++ b/src/App/Styles/ControlTemplates.xaml.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; + +namespace Bit.App.Styles +{ + public partial class ControlTemplates : ResourceDictionary + { + public ControlTemplates() + { + InitializeComponent(); + } + } +} + diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs index 65d2d2cfea3..02056efe1a4 100644 --- a/src/App/Utilities/ThemeManager.cs +++ b/src/App/Utilities/ThemeManager.cs @@ -58,6 +58,7 @@ public static void SetThemeStyle(string name, string autoDarkName, ResourceDicti // Base styles resources.MergedDictionaries.Add(new Base()); + resources.MergedDictionaries.Add(new ControlTemplates()); // Platform styles if (Device.RuntimePlatform == Device.Android) diff --git a/src/Core/ExternalLinksConstants.cs b/src/Core/ExternalLinksConstants.cs new file mode 100644 index 00000000000..28540d8ad77 --- /dev/null +++ b/src/Core/ExternalLinksConstants.cs @@ -0,0 +1,10 @@ +namespace Bit.Core +{ + public static class ExternalLinksConstants + { + public const string HELP_IMPORT_DATA = "https://bitwarden.com/help/import-data/"; + public const string HELP_CENTER = "https://bitwarden.com/help/"; + public const string CONTACT_SUPPORT = "https://bitwarden.com/contact/"; + public const string HELP_ABOUT_ORGANIZATIONS = "https://bitwarden.com/help/about-organizations/"; + } +}