From 1949a450fd44ccc16cb9d68d6c8ec6296c859b56 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Fri, 12 Jan 2024 13:54:34 -0300 Subject: [PATCH] PM-3350 Improved code safety adding a lot of try...catch and logging throughout the app. Also made the invoking on main thread safer on several places of the app. Additionally, on the GroupingsPageViewModel changed the code removing the old Xamarin hack and just using Replace directly instead of Clearing first to see if that fixes the crash we're having sometimes on the app. --- src/App/Platforms/iOS/AppDelegate.cs | 10 +- src/Core/App.xaml.cs | 142 +++++---- src/Core/Pages/Accounts/LockPage.xaml.cs | 160 ++++++---- src/Core/Pages/BaseContentPage.cs | 75 +++-- .../GeneratorHistoryPageViewModel.cs | 4 +- .../Pages/Generator/GeneratorPage.xaml.cs | 86 +++--- .../SendGroupingsPage.xaml.cs | 131 ++++---- .../SendGroupingsPageViewModel.cs | 87 +++--- .../Pages/Settings/ExportVaultPage.xaml.cs | 2 +- .../LoginPasswordlessRequestsListPage.xaml.cs | 10 +- src/Core/Pages/TabsPage.cs | 63 ++-- src/Core/Pages/Vault/AttachmentsPage.xaml.cs | 2 +- .../Pages/Vault/CipherAddEditPage.xaml.cs | 13 +- .../Pages/Vault/CipherDetailsPage.xaml.cs | 6 +- src/Core/Pages/Vault/CiphersPageViewModel.cs | 73 +++-- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 283 +++++++++++------- .../GroupingsPage/GroupingsPageViewModel.cs | 93 +++--- .../Vault/PasswordHistoryPageViewModel.cs | 2 +- .../BaseLockPasswordViewController.cs | 13 +- src/iOS.Core/Services/ClipboardService.cs | 2 +- 20 files changed, 745 insertions(+), 512 deletions(-) diff --git a/src/App/Platforms/iOS/AppDelegate.cs b/src/App/Platforms/iOS/AppDelegate.cs index 98d55b3ba..af4d6667b 100644 --- a/src/App/Platforms/iOS/AppDelegate.cs +++ b/src/App/Platforms/iOS/AppDelegate.cs @@ -54,10 +54,6 @@ namespace Bit.iOS _stateService = ServiceContainer.Resolve("stateService"); _eventService = ServiceContainer.Resolve("eventService"); - //LoadApplication(new App.App(null)); - //iOSCoreHelpers.AppearanceAdjustments(); - //ZXing.Net.Mobile.Forms.iOS.Platform.Init(); - ConnectToWatchIfNeededAsync().FireAndForget(); _broadcasterService.Subscribe(nameof(AppDelegate), async (message) => @@ -219,7 +215,11 @@ namespace Bit.iOS public override void DidEnterBackground(UIApplication uiApplication) { - _stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); + if (_stateService != null && _deviceActionService != null) + { + _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); + } + _messagingService?.Send("slept"); base.DidEnterBackground(uiApplication); } diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs index 38709023f..7b18301ec 100644 --- a/src/Core/App.xaml.cs +++ b/src/Core/App.xaml.cs @@ -35,6 +35,8 @@ namespace Bit.App private readonly IAccountsManager _accountsManager; private readonly IPushNotificationService _pushNotificationService; private readonly IConfigService _configService; + private readonly ILogger _logger; + private static bool _isResumed; // these variables are static because the app is launching new activities on notification click, creating new instances of App. private static bool _pendingCheckPasswordlessLoginRequests; @@ -155,6 +157,7 @@ namespace Bit.App _accountsManager = ServiceContainer.Resolve("accountsManager"); _pushNotificationService = ServiceContainer.Resolve(); _configService = ServiceContainer.Resolve(); + _logger = ServiceContainer.Resolve(); _accountsManager.Init(() => Options, this); @@ -169,7 +172,7 @@ namespace Bit.App var confirmed = true; var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? AppResources.Ok : details.ConfirmText; - MainThread.BeginInvokeOnMainThread(async () => + await MainThread.InvokeOnMainThreadAsync(async () => { if (!string.IsNullOrWhiteSpace(details.CancelText)) { @@ -183,20 +186,16 @@ namespace Bit.App _messagingService.Send("showDialogResolve", new Tuple(details.DialogId, confirmed)); }); } +#if IOS else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND) { - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - ResumedAsync().FireAndForget(); - } + ResumedAsync().FireAndForget(); } else if (message.Command == "slept") { - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - await SleptAsync(); - } + await SleptAsync(); } +#endif else if (message.Command == "migrated") { await Task.Delay(1000); @@ -213,7 +212,7 @@ namespace Bit.App Options.OtpData = new OtpData((string)message.Data); } - MainThread.InvokeOnMainThreadAsync(async () => + await MainThread.InvokeOnMainThreadAsync(async () => { if (MainPage is TabsPage tabsPage) { @@ -249,7 +248,7 @@ namespace Bit.App } else if (message.Command == "convertAccountToKeyConnector") { - MainThread.BeginInvokeOnMainThread(async () => + await MainThread.InvokeOnMainThreadAsync(async () => { await MainPage.Navigation.PushModalAsync( new NavigationPage(new RemoveMasterPasswordPage())); @@ -257,7 +256,7 @@ namespace Bit.App } else if (message.Command == Constants.ForceUpdatePassword) { - MainThread.BeginInvokeOnMainThread(async () => + await MainThread.InvokeOnMainThreadAsync(async () => { await MainPage.Navigation.PushModalAsync( new NavigationPage(new UpdateTempPasswordPage())); @@ -373,40 +372,52 @@ namespace Bit.App protected override async void OnStart() { - System.Diagnostics.Debug.WriteLine("XF App: OnStart"); - _isResumed = true; - await ClearCacheIfNeededAsync(); - Prime(); - if (string.IsNullOrWhiteSpace(Options.Uri)) + try { - var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, - _stateService); - if (!updated) + System.Diagnostics.Debug.WriteLine("XF App: OnStart"); + _isResumed = true; + await ClearCacheIfNeededAsync(); + Prime(); + if (string.IsNullOrWhiteSpace(Options.Uri)) { - SyncIfNeeded(); + var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, + _stateService); + if (!updated) + { + SyncIfNeeded(); + } } - } - if (_pendingCheckPasswordlessLoginRequests) - { - _messagingService.Send(Constants.PasswordlessLoginRequestKey); - } - if (DeviceInfo.Platform == DevicePlatform.Android) - { + if (_pendingCheckPasswordlessLoginRequests) + { + _messagingService.Send(Constants.PasswordlessLoginRequestKey); + } + #if ANDROID await _vaultTimeoutService.CheckVaultTimeoutAsync(); // Reset delay on every start _vaultTimeoutService.DelayLockAndLogoutMs = null; - } + #endif - await _configService.GetAsync(); - _messagingService.Send("startEventTimer"); + await _configService.GetAsync(); + _messagingService.Send("startEventTimer"); + } + catch (Exception ex) + { + _logger?.Exception(ex); + throw; + } } +#if ANDROID protected override async void OnSleep() +#else + protected override void OnSleep() +#endif { - System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); - _isResumed = false; - if (DeviceInfo.Platform == DevicePlatform.Android) + try { + System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); + _isResumed = false; +#if ANDROID var isLocked = await _vaultTimeoutService.IsLockedAsync(); if (!isLocked) { @@ -417,20 +428,34 @@ namespace Bit.App ClearAutofillUri(); } await SleptAsync(); +#endif + } + catch (Exception ex) + { + _logger?.Exception(ex); + throw; } } protected override void OnResume() { - System.Diagnostics.Debug.WriteLine("XF App: OnResume"); - _isResumed = true; - if (_pendingCheckPasswordlessLoginRequests) - { - _messagingService.Send(Constants.PasswordlessLoginRequestKey); - } - if (DeviceInfo.Platform == DevicePlatform.Android) + try { + System.Diagnostics.Debug.WriteLine("XF App: OnResume"); + _isResumed = true; + if (_pendingCheckPasswordlessLoginRequests) + { + _messagingService.Send(Constants.PasswordlessLoginRequestKey); + } +#if ANDROID ResumedAsync().FireAndForget(); +#endif + + } + catch (Exception ex) + { + _logger?.Exception(ex); + throw; } } @@ -517,14 +542,22 @@ namespace Bit.App { MainThread.BeginInvokeOnMainThread(() => { - Options.Uri = null; - if (isLocked) + try { - App.MainPage = new NavigationPage(new LockPage()); + Options.Uri = null; + if (isLocked) + { + App.MainPage = new NavigationPage(new LockPage()); + } + else + { + App.MainPage = new TabsPage(); + } } - else + catch (Exception ex) { - App.MainPage = new TabsPage(); + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; } }); }); @@ -549,7 +582,7 @@ namespace Bit.App ThemeManager.SetTheme(Resources); RequestedThemeChanged += (s, a) => { - UpdateThemeAsync(); + UpdateThemeAsync().FireAndForget(); }; _isResumed = true; #if IOS @@ -568,11 +601,18 @@ namespace Bit.App } Task.Run(async () => { - var lastSync = await _syncService.GetLastSyncAsync(); - if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30))) + try { - await Task.Delay(1000); - await _syncService.FullSyncAsync(false); + var lastSync = await _syncService.GetLastSyncAsync(); + if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30))) + { + await Task.Delay(1000); + await _syncService.FullSyncAsync(false); + } + } + catch (Exception ex) + { + _logger.Exception(ex); } }); } diff --git a/src/Core/Pages/Accounts/LockPage.xaml.cs b/src/Core/Pages/Accounts/LockPage.xaml.cs index 0cf421abe..6861f7bc2 100644 --- a/src/Core/Pages/Accounts/LockPage.xaml.cs +++ b/src/Core/Pages/Accounts/LockPage.xaml.cs @@ -4,6 +4,7 @@ using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; +using Bit.Core.Services; namespace Bit.App.Pages { @@ -26,16 +27,24 @@ namespace Bit.App.Pages _vm = BindingContext as LockPageViewModel; _vm.CheckPendingAuthRequests = checkPendingAuthRequests; _vm.Page = this; - _vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () => await UnlockedAsync()); + _vm.UnlockedAction = () => MainThread.BeginInvokeOnMainThread(async () => + { + try + { + await UnlockedAsync(); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; + } + }); - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - ToolbarItems.Add(_moreItem); - } - else - { - ToolbarItems.Add(_logOut); - } +#if IOS + ToolbarItems.Add(_moreItem); +#else + ToolbarItems.Add(_logOut); +#endif } public Entry SecretEntry @@ -65,52 +74,60 @@ namespace Bit.App.Pages protected override async void OnAppearing() { - base.OnAppearing(); - _broadcasterService.Subscribe(nameof(LockPage), message => + try { - if (message.Command == Constants.ClearSensitiveFields) + base.OnAppearing(); + _broadcasterService.Subscribe(nameof(LockPage), message => { - MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields); - } - }); - if (_appeared) - { - return; - } - - _appeared = true; - _mainContent.Content = _mainLayout; - - //Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS - await Task.Delay(50); - - _accountAvatar?.OnAppearing(); - - _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); - - await _vm.InitAsync(); - - _vm.FocusSecretEntry += PerformFocusSecretEntry; - - if (!_vm.BiometricEnabled) - { - RequestFocus(SecretEntry); - } - else - { - if (!_vm.HasMasterPassword && !_vm.PinEnabled) - { - _passwordGrid.IsVisible = false; - _unlockButton.IsVisible = false; - } - if (_autoPromptBiometric) - { - var tasks = Task.Run(async () => + if (message.Command == Constants.ClearSensitiveFields) { - await Task.Delay(500); - MainThread.BeginInvokeOnMainThread(async () => await _vm.PromptBiometricAsync()); - }); + MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields); + } + }); + if (_appeared) + { + return; } + + _appeared = true; + _mainContent.Content = _mainLayout; + + //Workaround: This delay allows the Avatar to correctly load on iOS. The cause of this issue is also likely connected with the race conditions issue when using loading modals in iOS + await Task.Delay(50); + + _accountAvatar?.OnAppearing(); + + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + + await _vm.InitAsync(); + + _vm.FocusSecretEntry += PerformFocusSecretEntry; + + if (!_vm.BiometricEnabled) + { + RequestFocus(SecretEntry); + } + else + { + if (!_vm.HasMasterPassword && !_vm.PinEnabled) + { + _passwordGrid.IsVisible = false; + _unlockButton.IsVisible = false; + } + if (_autoPromptBiometric) + { + var tasks = Task.Run(async () => + { + await Task.Delay(500); + await MainThread.InvokeOnMainThreadAsync(async () => await _vm.PromptBiometricAsync()); + }); + } + } + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; } } @@ -167,27 +184,44 @@ namespace Bit.App.Pages private async void Biometric_Clicked(object sender, EventArgs e) { - if (DoOnce()) + try { - await _vm.PromptBiometricAsync(); + if (DoOnce()) + { + await _vm.PromptBiometricAsync(); + } + + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; } } private async void More_Clicked(object sender, System.EventArgs e) { - await _accountListOverlay.HideAsync(); - - if (!DoOnce()) + try { - return; + await _accountListOverlay.HideAsync(); + + if (!DoOnce()) + { + return; + } + + var selection = await DisplayActionSheet(AppResources.Options, + AppResources.Cancel, null, AppResources.LogOut); + + if (selection == AppResources.LogOut) + { + await _vm.LogOutAsync(); + } } - - var selection = await DisplayActionSheet(AppResources.Options, - AppResources.Cancel, null, AppResources.LogOut); - - if (selection == AppResources.LogOut) + catch (Exception ex) { - await _vm.LogOutAsync(); + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; } } diff --git a/src/Core/Pages/BaseContentPage.cs b/src/Core/Pages/BaseContentPage.cs index fcb1ca1f9..d04c8a852 100644 --- a/src/Core/Pages/BaseContentPage.cs +++ b/src/Core/Pages/BaseContentPage.cs @@ -14,6 +14,7 @@ namespace Bit.App.Pages { private readonly LazyResolve _stateService = new LazyResolve(); private readonly LazyResolve _deviceActionService = new LazyResolve(); + private readonly LazyResolve _logger = new LazyResolve(); protected int ShowModalAnimationDelay = 400; protected int ShowPageAnimationDelay = 100; @@ -48,22 +49,38 @@ namespace Bit.App.Pages protected override async void OnNavigatedTo(NavigatedToEventArgs args) { - base.OnNavigatedTo(args); - - if (IsThemeDirty) + try { - UpdateOnThemeChanged(); + base.OnNavigatedTo(args); + + if (IsThemeDirty) + { + try + { + await UpdateOnThemeChanged(); + } + catch (Exception ex) + { + Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex); + // Don't rethrow on theme changed so the user can still continue on the app. + } + } + + await SaveActivityAsync(); + + if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo) + { + return; + } + _hasInitedOnNavigatedTo = true; + + await InitOnNavigatedToAsync(); } - - await SaveActivityAsync(); - - if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo) + catch (Exception ex) { - return; + Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; } - _hasInitedOnNavigatedTo = true; - - await InitOnNavigatedToAsync(); } protected virtual Task InitOnNavigatedToAsync() => Task.CompletedTask; @@ -116,29 +133,37 @@ namespace Bit.App.Pages { async Task DoWorkAsync() { - await workFunction.Invoke(); - if (sourceView != null) + try { - if (targetView != null) + await workFunction.Invoke(); + if (sourceView != null) { - targetView.Content = sourceView; - } - else - { - Content = sourceView; + if (targetView != null) + { + targetView.Content = sourceView; + } + else + { + Content = sourceView; + } } } + catch (Exception ex) + { + _logger.Value.Exception(ex); + throw; + } } - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - await DoWorkAsync(); - return; - } + +#if IOS + await DoWorkAsync(); +#else await Task.Run(async () => { await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay); MainThread.BeginInvokeOnMainThread(async () => await DoWorkAsync()); }); +#endif } protected void RequestFocus(InputView input) diff --git a/src/Core/Pages/Generator/GeneratorHistoryPageViewModel.cs b/src/Core/Pages/Generator/GeneratorHistoryPageViewModel.cs index ec03c7451..8ec43fcdb 100644 --- a/src/Core/Pages/Generator/GeneratorHistoryPageViewModel.cs +++ b/src/Core/Pages/Generator/GeneratorHistoryPageViewModel.cs @@ -42,7 +42,7 @@ namespace Bit.App.Pages public async Task InitAsync() { var history = await _passwordGenerationService.GetHistoryAsync(); - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { History.ResetWithRange(history ?? new List()); ShowNoData = History.Count == 0; @@ -66,7 +66,7 @@ namespace Bit.App.Pages { try { - await Device.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List())); + await MainThread.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List())); await InitAsync(); } diff --git a/src/Core/Pages/Generator/GeneratorPage.xaml.cs b/src/Core/Pages/Generator/GeneratorPage.xaml.cs index e0d336642..8ddb61dc3 100644 --- a/src/Core/Pages/Generator/GeneratorPage.xaml.cs +++ b/src/Core/Pages/Generator/GeneratorPage.xaml.cs @@ -15,6 +15,7 @@ namespace Bit.App.Pages public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage { private readonly IBroadcasterService _broadcasterService; + private readonly ILogger _logger; private GeneratorPageViewModel _vm; private readonly bool _fromTabPage; @@ -26,6 +27,8 @@ namespace Bit.App.Pages _tabsPage = tabsPage; InitializeComponent(); _broadcasterService = ServiceContainer.Resolve(); + _logger = ServiceContainer.Resolve(); + _vm = BindingContext as GeneratorPageViewModel; _vm.Page = this; _fromTabPage = fromTabPage; @@ -35,26 +38,21 @@ namespace Bit.App.Pages _vm.EmailWebsite = emailWebsite; _vm.EditMode = editMode; _vm.IosExtension = appOptions?.IosExtension ?? false; - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - var isIos = Device.RuntimePlatform == Device.iOS; + if (selectAction != null) { - if (isIos) - { - ToolbarItems.Add(_closeItem); - } +#if IOS + ToolbarItems.Add(_closeItem); +#endif ToolbarItems.Add(_selectItem); } else { - if (isIos) - { - ToolbarItems.Add(_moreItem); - } - else - { - ToolbarItems.Add(_historyItem); - } +#if IOS + ToolbarItems.Add(_moreItem); +#else + ToolbarItems.Add(_historyItem); +#endif } _typePicker.On().SetUpdateMode(UpdateMode.WhenFinished); _passwordTypePicker.On().SetUpdateMode(UpdateMode.WhenFinished); @@ -71,22 +69,30 @@ namespace Bit.App.Pages protected async override void OnAppearing() { - base.OnAppearing(); - - lblPassword.IsVisible = true; - - if (!_fromTabPage) + try { - await InitAsync(); - } + base.OnAppearing(); - _broadcasterService.Subscribe(nameof(GeneratorPage), (message) => - { - if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) + lblPassword.IsVisible = true; + + if (!_fromTabPage) { - Device.BeginInvokeOnMainThread(() => _vm.RedrawPassword()); + await InitAsync(); } - }); + + _broadcasterService.Subscribe(nameof(GeneratorPage), (message) => + { + if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) + { + MainThread.BeginInvokeOnMainThread(() => _vm.RedrawPassword()); + } + }); + } + catch (Exception ex) + { + _logger.Exception(ex); + throw; + } } protected override void OnDisappearing() @@ -100,27 +106,35 @@ namespace Bit.App.Pages protected override bool OnBackButtonPressed() { - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.Android && _tabsPage != null) +#if ANDROID + if (_tabsPage != null) { _tabsPage.ResetToVaultPage(); return true; } +#endif return base.OnBackButtonPressed(); } private async void More_Clicked(object sender, EventArgs e) { - if (!DoOnce()) + try { - return; + if (!DoOnce()) + { + return; + } + var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel, + null, AppResources.PasswordHistory); + if (selection == AppResources.PasswordHistory) + { + var page = new GeneratorHistoryPage(); + await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page)); + } } - var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel, - null, AppResources.PasswordHistory); - if (selection == AppResources.PasswordHistory) + catch (Exception ex) { - var page = new GeneratorHistoryPage(); - await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page)); + _logger.Exception(ex); } } @@ -144,7 +158,7 @@ namespace Bit.App.Pages { await base.UpdateOnThemeChanged(); - await Device.InvokeOnMainThreadAsync(() => + await MainThread.InvokeOnMainThreadAsync(() => { if (_vm != null) { diff --git a/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs b/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs index e9b0eacdd..f7320d8fb 100644 --- a/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs +++ b/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs @@ -18,6 +18,7 @@ namespace Bit.App.Pages private readonly ISyncService _syncService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly ISendService _sendService; + private readonly ILogger _logger; private readonly SendGroupingsPageViewModel _vm; private readonly string _pageName; @@ -33,6 +34,8 @@ namespace Bit.App.Pages _syncService = ServiceContainer.Resolve("syncService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _sendService = ServiceContainer.Resolve("sendService"); + _logger = ServiceContainer.Resolve(); + _vm = BindingContext as SendGroupingsPageViewModel; _vm.Page = this; _vm.MainPage = mainPage; @@ -43,85 +46,89 @@ namespace Bit.App.Pages _vm.PageTitle = pageTitle; } - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.iOS) +#if IOS + _absLayout.Children.Remove(_fab); + if (type == null) { - _absLayout.Children.Remove(_fab); - if (type == null) - { - ToolbarItems.Add(_aboutIconItem); - } - ToolbarItems.Add(_addItem); - } - else - { - ToolbarItems.Add(_syncItem); - ToolbarItems.Add(_lockItem); - ToolbarItems.Add(_aboutTextItem); + ToolbarItems.Add(_aboutIconItem); } + ToolbarItems.Add(_addItem); +#else + ToolbarItems.Add(_syncItem); + ToolbarItems.Add(_lockItem); + ToolbarItems.Add(_aboutTextItem); +#endif } protected override async void OnAppearing() { - base.OnAppearing(); - if (_syncService.SyncInProgress) + try { - IsBusy = true; - } - - _broadcasterService.Subscribe(_pageName, async (message) => - { - try + base.OnAppearing(); + if (_syncService.SyncInProgress) { - if (message.Command == "syncStarted") - { - Device.BeginInvokeOnMainThread(() => IsBusy = true); - } - else if (message.Command == "syncCompleted" || message.Command == "sendUpdated") - { - await Task.Delay(500); - Device.BeginInvokeOnMainThread(() => - { - IsBusy = false; - if (_vm.LoadedOnce) - { - var task = _vm.LoadAsync(); - } - }); - } + IsBusy = true; } - catch (Exception ex) - { - LoggerHelper.LogEvenIfCantBeResolved(ex); - } - }); - await LoadOnAppearedAsync(_mainLayout, false, async () => - { - if (!_syncService.SyncInProgress || (await _sendService.GetAllAsync()).Any()) + _broadcasterService.Subscribe(_pageName, async (message) => { try { - await _vm.LoadAsync(); + if (message.Command == "syncStarted") + { + MainThread.BeginInvokeOnMainThread(() => IsBusy = true); + } + else if (message.Command == "syncCompleted" || message.Command == "sendUpdated") + { + await Task.Delay(500); + await MainThread.InvokeOnMainThreadAsync(() => + { + IsBusy = false; + if (_vm.LoadedOnce) + { + var task = _vm.LoadAsync(); + } + }); + } } - catch (Exception e) when (e.Message.Contains("No key.")) + catch (Exception ex) { - await Task.Delay(1000); - await _vm.LoadAsync(); + LoggerHelper.LogEvenIfCantBeResolved(ex); } - } - else - { - await Task.Delay(5000); - if (!_vm.Loaded) - { - await _vm.LoadAsync(); - } - } + }); - AdjustToolbar(); - await CheckAddRequest(); - }, _mainContent); + await LoadOnAppearedAsync(_mainLayout, false, async () => + { + if (!_syncService.SyncInProgress || (await _sendService.GetAllAsync()).Any()) + { + try + { + await _vm.LoadAsync(); + } + catch (Exception e) when (e.Message.Contains("No key.")) + { + await Task.Delay(1000); + await _vm.LoadAsync(); + } + } + else + { + await Task.Delay(5000); + if (!_vm.Loaded) + { + await _vm.LoadAsync(); + } + } + + AdjustToolbar(); + await CheckAddRequest(); + }, _mainContent); + } + catch (Exception ex) + { + _logger.Exception(ex); + throw; + } } protected override void OnDisappearing() diff --git a/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs b/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs index 1050e223d..239f9421e 100644 --- a/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs +++ b/src/Core/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs @@ -1,22 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.App.Abstractions; -using Bit.Core.Resources.Localization; +using Bit.App.Abstractions; using Bit.App.Utilities; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.View; +using Bit.Core.Resources.Localization; using Bit.Core.Utilities; -using DeviceType = Bit.Core.Enums.DeviceType; -using Microsoft.Maui.Networking; -using Microsoft.Maui.Devices; -using Microsoft.Maui.Controls; -using Microsoft.Maui; - namespace Bit.App.Pages { public class SendGroupingsPageViewModel : BaseViewModel @@ -117,28 +106,38 @@ namespace Bit.App.Pages { return; } - var authed = await _stateService.IsAuthenticatedAsync(); - if (!authed) + + try { - return; + var authed = await _stateService.IsAuthenticatedAsync(); + if (!authed) + { + return; + } + if (await _vaultTimeoutService.IsLockedAsync()) + { + return; + } + if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) + { + SyncRefreshing = true; + await _syncService.FullSyncAsync(false); + return; + } + + _doingLoad = true; + LoadedOnce = true; + ShowNoData = false; + Loading = true; + ShowList = false; + SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); } - if (await _vaultTimeoutService.IsLockedAsync()) + catch (Exception ex) { - return; - } - if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) - { - SyncRefreshing = true; - await _syncService.FullSyncAsync(false); - return; + _logger.Value.Exception(ex); + throw; } - _doingLoad = true; - LoadedOnce = true; - ShowNoData = false; - Loading = true; - ShowList = false; - SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); var groupedSends = new List(); var page = Page as SendGroupingsPage; @@ -146,8 +145,11 @@ namespace Bit.App.Pages { await LoadDataAsync(); - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS; +#if IOS + var uppercaseGroupNames = true; +#else + var uppercaseGroupNames = false; +#endif if (MainPage) { groupedSends.Add(new SendGroupingsPageListGroup( @@ -208,6 +210,11 @@ namespace Bit.App.Pages } } } + catch (Exception ex) + { + _logger.Value.Exception(ex); + throw; + } finally { _doingLoad = false; @@ -315,14 +322,22 @@ namespace Bit.App.Pages private async void SendOptionsAsync(SendView send) { - if ((Page as BaseContentPage).DoOnce()) + try { - var selection = await AppHelpers.SendListOptions(Page, send); - if (selection == AppResources.RemovePassword || selection == AppResources.Delete) + if ((Page as BaseContentPage).DoOnce()) { - await LoadAsync(); + var selection = await AppHelpers.SendListOptions(Page, send); + if (selection == AppResources.RemovePassword || selection == AppResources.Delete) + { + await LoadAsync(); + } } } + catch (Exception ex) + { + _logger.Value.Exception(ex); + throw; + } } } } diff --git a/src/Core/Pages/Settings/ExportVaultPage.xaml.cs b/src/Core/Pages/Settings/ExportVaultPage.xaml.cs index 4d7a4276c..be8d8f94e 100644 --- a/src/Core/Pages/Settings/ExportVaultPage.xaml.cs +++ b/src/Core/Pages/Settings/ExportVaultPage.xaml.cs @@ -29,7 +29,7 @@ namespace Bit.App.Pages { if (message.Command == "selectSaveFileResult") { - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { var data = message.Data as Tuple; if (data == null) diff --git a/src/Core/Pages/Settings/LoginPasswordlessRequestsListPage.xaml.cs b/src/Core/Pages/Settings/LoginPasswordlessRequestsListPage.xaml.cs index c3c25583c..0a68ab640 100644 --- a/src/Core/Pages/Settings/LoginPasswordlessRequestsListPage.xaml.cs +++ b/src/Core/Pages/Settings/LoginPasswordlessRequestsListPage.xaml.cs @@ -49,12 +49,10 @@ namespace Bit.App.Pages private void UpdatePlaceholder() { - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.Android) - { - MainThread.BeginInvokeOnMainThread(() => - _emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark")); - } +#if ANDROID + MainThread.BeginInvokeOnMainThread(() => + _emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark")); +#endif } } } diff --git a/src/Core/Pages/TabsPage.cs b/src/Core/Pages/TabsPage.cs index c9d02e320..0371b44a9 100644 --- a/src/Core/Pages/TabsPage.cs +++ b/src/Core/Pages/TabsPage.cs @@ -10,6 +10,7 @@ using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Utilities; +using Bit.Core.Services; namespace Bit.App.Pages { @@ -87,21 +88,29 @@ namespace Bit.App.Pages protected override async void OnAppearing() { - base.OnAppearing(); - _broadcasterService.Subscribe(nameof(TabsPage), async (message) => + try { - if (message.Command == "syncCompleted") + base.OnAppearing(); + _broadcasterService.Subscribe(nameof(TabsPage), async (message) => { - MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync()); + if (message.Command == "syncCompleted") + { + MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync()); + } + }); + await UpdateVaultButtonTitleAsync(); + if (await _keyConnectorService.UserNeedsMigrationAsync()) + { + _messagingService.Send("convertAccountToKeyConnector"); } - }); - await UpdateVaultButtonTitleAsync(); - if (await _keyConnectorService.UserNeedsMigrationAsync()) - { - _messagingService.Send("convertAccountToKeyConnector"); - } - await ForcePasswordResetIfNeededAsync(); + await ForcePasswordResetIfNeededAsync(); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; + } } private async Task ForcePasswordResetIfNeededAsync() @@ -174,23 +183,31 @@ namespace Bit.App.Pages protected override async void OnCurrentPageChanged() { - if (CurrentPage is NavigationPage navPage) + try { - if (_groupingsPage?.RootPage is GroupingsPage groupingsPage) + if (CurrentPage is NavigationPage navPage) { - await groupingsPage.HideAccountSwitchingOverlayAsync(); - } + if (_groupingsPage?.RootPage is GroupingsPage groupingsPage) + { + await groupingsPage.HideAccountSwitchingOverlayAsync(); + } - _messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY); - if (navPage.RootPage is GroupingsPage) - { - // Load something? - } - else if (navPage.RootPage is GeneratorPage genPage) - { - await genPage.InitAsync(); + _messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY); + if (navPage.RootPage is GroupingsPage) + { + // Load something? + } + else if (navPage.RootPage is GeneratorPage genPage) + { + await genPage.InitAsync(); + } } } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; + } } public void OnPageReselected() diff --git a/src/Core/Pages/Vault/AttachmentsPage.xaml.cs b/src/Core/Pages/Vault/AttachmentsPage.xaml.cs index 319a2ffdd..9e56148db 100644 --- a/src/Core/Pages/Vault/AttachmentsPage.xaml.cs +++ b/src/Core/Pages/Vault/AttachmentsPage.xaml.cs @@ -33,7 +33,7 @@ namespace Bit.App.Pages { if (message.Command == "selectFileResult") { - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { var data = message.Data as Tuple; _vm.FileData = data.Item1; diff --git a/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs b/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs index 792e4a63a..1ff0be66c 100644 --- a/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs +++ b/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs @@ -7,6 +7,7 @@ using Bit.Core.Enums; using Bit.Core.Utilities; using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; +using Bit.Core.Services; namespace Bit.App.Pages { @@ -263,8 +264,16 @@ namespace Bit.App.Pages { MainThread.BeginInvokeOnMainThread(async () => { - await Navigation.PopModalAsync(); - await _vm.UpdateTotpKeyAsync(key); + try + { + await Navigation.PopModalAsync(); + await _vm.UpdateTotpKeyAsync(key); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; + } }); }); diff --git a/src/Core/Pages/Vault/CipherDetailsPage.xaml.cs b/src/Core/Pages/Vault/CipherDetailsPage.xaml.cs index 79a783011..f10fbee58 100644 --- a/src/Core/Pages/Vault/CipherDetailsPage.xaml.cs +++ b/src/Core/Pages/Vault/CipherDetailsPage.xaml.cs @@ -63,12 +63,12 @@ namespace Bit.App.Pages { if (message.Command == "syncStarted") { - Device.BeginInvokeOnMainThread(() => IsBusy = true); + MainThread.BeginInvokeOnMainThread(() => IsBusy = true); } else if (message.Command == "syncCompleted") { await Task.Delay(500); - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { IsBusy = false; if (message.Data is Dictionary data && data.ContainsKey("successfully")) @@ -83,7 +83,7 @@ namespace Bit.App.Pages } else if (message.Command == "selectSaveFileResult") { - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { var data = message.Data as Tuple; if (data == null) diff --git a/src/Core/Pages/Vault/CiphersPageViewModel.cs b/src/Core/Pages/Vault/CiphersPageViewModel.cs index 44c786fd4..8a30deb8a 100644 --- a/src/Core/Pages/Vault/CiphersPageViewModel.cs +++ b/src/Core/Pages/Vault/CiphersPageViewModel.cs @@ -109,52 +109,59 @@ namespace Bit.App.Pages var cts = new CancellationTokenSource(); Task.Run(async () => { - List ciphers = null; - var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1; - var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText); - if (searchable || shouldShowAllWhenEmpty) + try { - if (timeout != null) + List ciphers = null; + var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1; + var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText); + if (searchable || shouldShowAllWhenEmpty) { - await Task.Delay(timeout.Value); - } - if (searchText != (Page as CiphersPage).SearchBar.Text - && - !shouldShowAllWhenEmpty) - { - return; - } + if (timeout != null) + { + await Task.Delay(timeout.Value); + } + if (searchText != (Page as CiphersPage).SearchBar.Text + && + !shouldShowAllWhenEmpty) + { + return; + } - previousCts?.Cancel(); - try - { - var vaultFilteredCiphers = await GetAllCiphersAsync(); - if (!shouldShowAllWhenEmpty) + previousCts?.Cancel(); + try { - ciphers = await _searchService.SearchCiphersAsync(searchText, - Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token); + var vaultFilteredCiphers = await GetAllCiphersAsync(); + if (!shouldShowAllWhenEmpty) + { + ciphers = await _searchService.SearchCiphersAsync(searchText, + Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token); + } + else + { + ciphers = vaultFilteredCiphers; + } + cts.Token.ThrowIfCancellationRequested(); } - else + catch (OperationCanceledException) { - ciphers = vaultFilteredCiphers; + return; } - cts.Token.ThrowIfCancellationRequested(); } - catch (OperationCanceledException) + if (ciphers == null) { - return; + ciphers = new List(); } + MainThread.BeginInvokeOnMainThread(() => + { + Ciphers.ResetWithRange(ciphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList()); + ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0; + ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData; + }); } - if (ciphers == null) + catch (Exception ex) { - ciphers = new List(); + _logger.Exception(ex); } - MainThread.BeginInvokeOnMainThread(() => - { - Ciphers.ResetWithRange(ciphers.Select(c => new CipherItemViewModel(c, _websiteIconsEnabled)).ToList()); - ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0; - ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData; - }); }, cts.Token); _searchCancellationTokenSource = cts; } diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 137626807..46d65d87c 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -1,9 +1,9 @@ using Bit.App.Abstractions; using Bit.App.Controls; -using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; +using Bit.Core.Resources.Localization; using Bit.Core.Services; using Bit.Core.Utilities; @@ -19,6 +19,7 @@ namespace Bit.App.Pages private readonly ICipherService _cipherService; private readonly IDeviceActionService _deviceActionService; private readonly IPlatformUtilsService _platformUtilsService; + private readonly ILogger _logger; private readonly GroupingsPageViewModel _vm; private readonly string _pageName; @@ -39,6 +40,8 @@ namespace Bit.App.Pages _cipherService = ServiceContainer.Resolve("cipherService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + _logger = ServiceContainer.Resolve(); + _vm = BindingContext as GroupingsPageViewModel; _vm.Page = this; _vm.MainPage = mainPage; @@ -57,17 +60,14 @@ namespace Bit.App.Pages _vm.VaultFilterDescription = vaultFilterSelection; } - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - _absLayout.Children.Remove(_fab); - ToolbarItems.Add(_addItem); - } - else - { - ToolbarItems.Add(_syncItem); - ToolbarItems.Add(_lockItem); - ToolbarItems.Add(_exitItem); - } +#if IOS + _absLayout.Children.Remove(_fab); + ToolbarItems.Add(_addItem); +#else + ToolbarItems.Add(_syncItem); + ToolbarItems.Add(_lockItem); + ToolbarItems.Add(_exitItem); +#endif if (deleted || showTotp) { _absLayout.Children.Remove(_fab); @@ -81,107 +81,115 @@ namespace Bit.App.Pages protected override async void OnAppearing() { - base.OnAppearing(); - if (_syncService.SyncInProgress) + try { - IsBusy = true; - } - - _accountAvatar?.OnAppearing(); - if (_vm.MainPage) - { - _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); - } - - _broadcasterService.Subscribe(_pageName, async (message) => - { - try + base.OnAppearing(); + if (_syncService.SyncInProgress) { - if (message.Command == "syncStarted") + IsBusy = true; + } + + _accountAvatar?.OnAppearing(); + if (_vm.MainPage) + { + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + } + + _broadcasterService.Subscribe(_pageName, async (message) => + { + try { - MainThread.BeginInvokeOnMainThread(() => IsBusy = true); - } - else if (message.Command == "syncCompleted") - { - await Task.Delay(500); - if (_vm.MainPage) + if (message.Command == "syncStarted") { - _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + MainThread.BeginInvokeOnMainThread(() => IsBusy = true); } - MainThread.BeginInvokeOnMainThread(() => + else if (message.Command == "syncCompleted") { - IsBusy = false; - if (_vm.LoadedOnce) + await Task.Delay(500); + if (_vm.MainPage) { - var task = _vm.LoadAsync(); + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); } - }); + MainThread.BeginInvokeOnMainThread(() => + { + IsBusy = false; + if (_vm.LoadedOnce) + { + var task = _vm.LoadAsync(); + } + }); + } } - } - catch (Exception ex) - { - LoggerHelper.LogEvenIfCantBeResolved(ex); - } - }); - - await LoadOnAppearedAsync(_mainLayout, false, async () => - { - if (_previousPage == null) - { - if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any()) + catch (Exception ex) { - try - { - await _vm.LoadAsync(); - } - catch (Exception e) when (e.Message.Contains("No key.")) - { - await Task.Delay(1000); - await _vm.LoadAsync(); - } + LoggerHelper.LogEvenIfCantBeResolved(ex); } - else + }); + + await LoadOnAppearedAsync(_mainLayout, false, async () => + { + if (_previousPage == null) { - await Task.Delay(5000); - if (!_vm.Loaded) + if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any()) { - await _vm.LoadAsync(); + try + { + await _vm.LoadAsync(); + } + catch (Exception e) when (e.Message.Contains("No key.")) + { + await Task.Delay(1000); + await _vm.LoadAsync(); + } + } + else + { + await Task.Delay(5000); + if (!_vm.Loaded) + { + await _vm.LoadAsync(); + } } } - } - await ShowPreviousPageAsync(); - AdjustToolbar(); - }, _mainContent); + await ShowPreviousPageAsync(); + AdjustToolbar(); + }, _mainContent); - if (!_vm.MainPage) - { - return; - } - - // Push registration - var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync(); - lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); - if (DeviceInfo.Platform == DevicePlatform.iOS) - { - var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync(); - if (!pushPromptShow.GetValueOrDefault(false)) + if (!_vm.MainPage) { - await _stateService.SetPushInitialPromptShownAsync(true); - await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert, - AppResources.OkGotIt); + return; } - if (!pushPromptShow.GetValueOrDefault(false) || - DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1)) + + // Push registration + var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync(); + lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); + if (DeviceInfo.Platform == DevicePlatform.iOS) { - await _pushNotificationService.RegisterAsync(); + var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync(); + if (!pushPromptShow.GetValueOrDefault(false)) + { + await _stateService.SetPushInitialPromptShownAsync(true); + await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert, + AppResources.OkGotIt); + } + if (!pushPromptShow.GetValueOrDefault(false) || + DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1)) + { + await _pushNotificationService.RegisterAsync(); + } + } + else if (DeviceInfo.Platform == DevicePlatform.Android) + { + if (DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1)) + { + await _pushNotificationService.RegisterAsync(); + } } } - else if (DeviceInfo.Platform == DevicePlatform.Android) + catch (Exception ex) { - if (DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1)) - { - await _pushNotificationService.RegisterAsync(); - } + _logger.Exception(ex); + throw; } } @@ -195,14 +203,22 @@ namespace Bit.App.Pages return false; } - protected override async void OnDisappearing() + protected override void OnDisappearing() { - base.OnDisappearing(); - IsBusy = false; - _vm.StopCiphersTotpTick().FireAndForget(); - _broadcasterService.Unsubscribe(_pageName); - _vm.DisableRefreshing(); - _accountAvatar?.OnDisappearing(); + try + { + base.OnDisappearing(); + IsBusy = false; + _vm.StopCiphersTotpTick().FireAndForget(); + _broadcasterService.Unsubscribe(_pageName); + _vm.DisableRefreshing(); + _accountAvatar?.OnDisappearing(); + } + catch (Exception ex) + { + _logger.Exception(ex); + throw; + } } private async void RowSelected(object sender, SelectionChangedEventArgs e) @@ -264,45 +280,80 @@ namespace Bit.App.Pages private async void Search_Clicked(object sender, EventArgs e) { - await _accountListOverlay.HideAsync(); - if (DoOnce()) + try { - var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted); - await Navigation.PushModalAsync(new NavigationPage(page)); + await _accountListOverlay.HideAsync(); + if (DoOnce()) + { + var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted); + await Navigation.PushModalAsync(new NavigationPage(page)); + } + } + catch (Exception ex) + { + _logger.Exception(ex); } } private async void Sync_Clicked(object sender, EventArgs e) { - await _accountListOverlay.HideAsync(); - await _vm.SyncAsync(); + try + { + await _accountListOverlay.HideAsync(); + await _vm.SyncAsync(); + } + catch (Exception ex) + { + _logger.Exception(ex); + } } private async void Lock_Clicked(object sender, EventArgs e) { - await _accountListOverlay.HideAsync(); - await _vaultTimeoutService.LockAsync(true, true); + try + { + await _accountListOverlay.HideAsync(); + await _vaultTimeoutService.LockAsync(true, true); + } + catch (Exception ex) + { + _logger.Exception(ex); + } } private async void Exit_Clicked(object sender, EventArgs e) { - await _accountListOverlay.HideAsync(); - await _vm.ExitAsync(); + try + { + await _accountListOverlay.HideAsync(); + await _vm.ExitAsync(); + } + catch (Exception ex) + { + _logger.Exception(ex); + } } private async void AddButton_Clicked(object sender, EventArgs e) { - var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android; - await _accountListOverlay.HideAsync(); - if (skipAction) + try { - // Account list in the process of closing via tapping on invisible FAB, skip this attempt - return; + var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android; + await _accountListOverlay.HideAsync(); + if (skipAction) + { + // Account list in the process of closing via tapping on invisible FAB, skip this attempt + return; + } + if (!_vm.Deleted && DoOnce()) + { + var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId()); + await Navigation.PushModalAsync(new NavigationPage(page)); + } } - if (!_vm.Deleted && DoOnce()) + catch (Exception ex) { - var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId()); - await Navigation.PushModalAsync(new NavigationPage(page)); + _logger.Exception(ex); } } diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 801eb261c..a249b0b35 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -166,41 +166,52 @@ namespace Bit.App.Pages { return; } - var authed = await _stateService.IsAuthenticatedAsync(); - if (!authed) - { - return; - } - if (await _vaultTimeoutService.IsLockedAsync()) - { - return; - } - if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) - { - SyncRefreshing = true; - await _syncService.SyncPasswordlessLoginRequestsAsync(); - await _syncService.FullSyncAsync(false); - return; - } - _deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(); - - await InitVaultFilterAsync(MainPage); - if (MainPage) + try { - PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault; + var authed = await _stateService.IsAuthenticatedAsync(); + if (!authed) + { + return; + } + if (await _vaultTimeoutService.IsLockedAsync()) + { + return; + } + if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) + { + SyncRefreshing = true; + await _syncService.SyncPasswordlessLoginRequestsAsync(); + await _syncService.FullSyncAsync(false); + return; + } + + _deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(); + + await InitVaultFilterAsync(MainPage); + if (MainPage) + { + PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault; + } + + _doingLoad = true; + LoadedOnce = true; + ShowNoData = false; + Loading = true; + ShowList = false; + ShowAddCipherButton = !Deleted; + + _websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true; + } + catch (Exception ex) + { + _logger.Exception(ex); + throw; } - _doingLoad = true; - LoadedOnce = true; - ShowNoData = false; - Loading = true; - ShowList = false; - ShowAddCipherButton = !Deleted; var groupedItems = new List(); var page = Page as GroupingsPage; - _websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true; try { await LoadDataAsync(); @@ -307,11 +318,6 @@ namespace Bit.App.Pages await MainThread.InvokeOnMainThreadAsync(() => { -#if IOS - // HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info - // because of update to XF v5.0.0.2401 - GroupedItems.Clear(); -#endif GroupedItems.ReplaceRange(items); }); } @@ -335,23 +341,22 @@ namespace Bit.App.Pages await MainThread.InvokeOnMainThreadAsync(() => { - if (groupedItems.Any()) - { -#if IOS - // HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info - // because of update to XF v5.0.0.2401 - GroupedItems.Clear(); -#endif - GroupedItems.ReplaceRange(new List { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); - GroupedItems.AddRange(items); - } - else + if (!groupedItems.Any()) { GroupedItems.Clear(); + return; } + + GroupedItems.ReplaceRange(new List { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); + GroupedItems.AddRange(items); }); } } + catch (Exception ex) + { + _logger.Exception(ex); + throw; + } finally { _doingLoad = false; diff --git a/src/Core/Pages/Vault/PasswordHistoryPageViewModel.cs b/src/Core/Pages/Vault/PasswordHistoryPageViewModel.cs index bfc699ba9..3ab5a182a 100644 --- a/src/Core/Pages/Vault/PasswordHistoryPageViewModel.cs +++ b/src/Core/Pages/Vault/PasswordHistoryPageViewModel.cs @@ -42,7 +42,7 @@ namespace Bit.App.Pages { var cipher = await _cipherService.GetAsync(CipherId); var decCipher = await cipher.DecryptAsync(); - Device.BeginInvokeOnMainThread(() => + MainThread.BeginInvokeOnMainThread(() => { History.ResetWithRange(decCipher.PasswordHistory ?? new List()); ShowNoData = History.Count == 0; diff --git a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs index 53161a44c..e01502558 100644 --- a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs @@ -204,7 +204,18 @@ namespace Bit.iOS.Core.Controllers var tasks = Task.Run(async () => { await Task.Delay(500); - NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync()); + NSRunLoop.Main.BeginInvokeOnMainThread(async () => + { + try + { + await PromptBiometricAsync(); + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + throw; + } + }); }); } } diff --git a/src/iOS.Core/Services/ClipboardService.cs b/src/iOS.Core/Services/ClipboardService.cs index 0db6b1150..ae72ef194 100644 --- a/src/iOS.Core/Services/ClipboardService.cs +++ b/src/iOS.Core/Services/ClipboardService.cs @@ -31,7 +31,7 @@ namespace Bit.iOS.Core.Services var dictArr = new NSDictionary[1]; dictArr[0] = new NSDictionary(new NSString(UTType.UTF8PlainText), new NSString(text)); - Device.BeginInvokeOnMainThread(() => UIPasteboard.General.SetItems(dictArr, new UIPasteboardOptions + MainThread.BeginInvokeOnMainThread(() => UIPasteboard.General.SetItems(dictArr, new UIPasteboardOptions { LocalOnly = true, ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null