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.

This commit is contained in:
Federico Maccaroni 2024-01-12 13:54:34 -03:00
parent 27fa79e0bd
commit 1949a450fd
No known key found for this signature in database
GPG key ID: 5D233F8F2B034536
20 changed files with 745 additions and 512 deletions

View file

@ -54,10 +54,6 @@ namespace Bit.iOS
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
//LoadApplication(new App.App(null));
//iOSCoreHelpers.AppearanceAdjustments();
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
ConnectToWatchIfNeededAsync().FireAndForget(); ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) => _broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
@ -219,7 +215,11 @@ namespace Bit.iOS
public override void DidEnterBackground(UIApplication uiApplication) public override void DidEnterBackground(UIApplication uiApplication)
{ {
_stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); if (_stateService != null && _deviceActionService != null)
{
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
_messagingService?.Send("slept"); _messagingService?.Send("slept");
base.DidEnterBackground(uiApplication); base.DidEnterBackground(uiApplication);
} }

View file

@ -35,6 +35,8 @@ namespace Bit.App
private readonly IAccountsManager _accountsManager; private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ILogger _logger;
private static bool _isResumed; private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App. // these variables are static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests; private static bool _pendingCheckPasswordlessLoginRequests;
@ -155,6 +157,7 @@ namespace Bit.App
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager"); _accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>(); _pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_configService = ServiceContainer.Resolve<IConfigService>(); _configService = ServiceContainer.Resolve<IConfigService>();
_logger = ServiceContainer.Resolve<ILogger>();
_accountsManager.Init(() => Options, this); _accountsManager.Init(() => Options, this);
@ -169,7 +172,7 @@ namespace Bit.App
var confirmed = true; var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText; AppResources.Ok : details.ConfirmText;
MainThread.BeginInvokeOnMainThread(async () => await MainThread.InvokeOnMainThreadAsync(async () =>
{ {
if (!string.IsNullOrWhiteSpace(details.CancelText)) if (!string.IsNullOrWhiteSpace(details.CancelText))
{ {
@ -183,20 +186,16 @@ namespace Bit.App
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
}); });
} }
#if IOS
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND) else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{ {
if (DeviceInfo.Platform == DevicePlatform.iOS) ResumedAsync().FireAndForget();
{
ResumedAsync().FireAndForget();
}
} }
else if (message.Command == "slept") else if (message.Command == "slept")
{ {
if (DeviceInfo.Platform == DevicePlatform.iOS) await SleptAsync();
{
await SleptAsync();
}
} }
#endif
else if (message.Command == "migrated") else if (message.Command == "migrated")
{ {
await Task.Delay(1000); await Task.Delay(1000);
@ -213,7 +212,7 @@ namespace Bit.App
Options.OtpData = new OtpData((string)message.Data); Options.OtpData = new OtpData((string)message.Data);
} }
MainThread.InvokeOnMainThreadAsync(async () => await MainThread.InvokeOnMainThreadAsync(async () =>
{ {
if (MainPage is TabsPage tabsPage) if (MainPage is TabsPage tabsPage)
{ {
@ -249,7 +248,7 @@ namespace Bit.App
} }
else if (message.Command == "convertAccountToKeyConnector") else if (message.Command == "convertAccountToKeyConnector")
{ {
MainThread.BeginInvokeOnMainThread(async () => await MainThread.InvokeOnMainThreadAsync(async () =>
{ {
await MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
@ -257,7 +256,7 @@ namespace Bit.App
} }
else if (message.Command == Constants.ForceUpdatePassword) else if (message.Command == Constants.ForceUpdatePassword)
{ {
MainThread.BeginInvokeOnMainThread(async () => await MainThread.InvokeOnMainThreadAsync(async () =>
{ {
await MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage())); new NavigationPage(new UpdateTempPasswordPage()));
@ -373,40 +372,52 @@ namespace Bit.App
protected override async void OnStart() protected override async void OnStart()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnStart"); try
_isResumed = true;
await ClearCacheIfNeededAsync();
Prime();
if (string.IsNullOrWhiteSpace(Options.Uri))
{ {
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_stateService); _isResumed = true;
if (!updated) await ClearCacheIfNeededAsync();
Prime();
if (string.IsNullOrWhiteSpace(Options.Uri))
{ {
SyncIfNeeded(); var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_stateService);
if (!updated)
{
SyncIfNeeded();
}
} }
} if (_pendingCheckPasswordlessLoginRequests)
if (_pendingCheckPasswordlessLoginRequests) {
{ _messagingService.Send(Constants.PasswordlessLoginRequestKey);
_messagingService.Send(Constants.PasswordlessLoginRequestKey); }
} #if ANDROID
if (DeviceInfo.Platform == DevicePlatform.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start // Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null; _vaultTimeoutService.DelayLockAndLogoutMs = null;
} #endif
await _configService.GetAsync(); await _configService.GetAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
}
} }
#if ANDROID
protected override async void OnSleep() protected override async void OnSleep()
#else
protected override void OnSleep()
#endif
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); try
_isResumed = false;
if (DeviceInfo.Platform == DevicePlatform.Android)
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false;
#if ANDROID
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
@ -417,20 +428,34 @@ namespace Bit.App
ClearAutofillUri(); ClearAutofillUri();
} }
await SleptAsync(); await SleptAsync();
#endif
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
} }
} }
protected override void OnResume() protected override void OnResume()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnResume"); try
_isResumed = true;
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
if (DeviceInfo.Platform == DevicePlatform.Android)
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_isResumed = true;
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
#if ANDROID
ResumedAsync().FireAndForget(); ResumedAsync().FireAndForget();
#endif
}
catch (Exception ex)
{
_logger?.Exception(ex);
throw;
} }
} }
@ -517,14 +542,22 @@ namespace Bit.App
{ {
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
Options.Uri = null; try
if (isLocked)
{ {
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); ThemeManager.SetTheme(Resources);
RequestedThemeChanged += (s, a) => RequestedThemeChanged += (s, a) =>
{ {
UpdateThemeAsync(); UpdateThemeAsync().FireAndForget();
}; };
_isResumed = true; _isResumed = true;
#if IOS #if IOS
@ -568,11 +601,18 @@ namespace Bit.App
} }
Task.Run(async () => Task.Run(async () =>
{ {
var lastSync = await _syncService.GetLastSyncAsync(); try
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{ {
await Task.Delay(1000); var lastSync = await _syncService.GetLastSyncAsync();
await _syncService.FullSyncAsync(false); if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{
await Task.Delay(1000);
await _syncService.FullSyncAsync(false);
}
}
catch (Exception ex)
{
_logger.Exception(ex);
} }
}); });
} }

View file

@ -4,6 +4,7 @@ using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Services;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -26,16 +27,24 @@ namespace Bit.App.Pages
_vm = BindingContext as LockPageViewModel; _vm = BindingContext as LockPageViewModel;
_vm.CheckPendingAuthRequests = checkPendingAuthRequests; _vm.CheckPendingAuthRequests = checkPendingAuthRequests;
_vm.Page = this; _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) #if IOS
{ ToolbarItems.Add(_moreItem);
ToolbarItems.Add(_moreItem); #else
} ToolbarItems.Add(_logOut);
else #endif
{
ToolbarItems.Add(_logOut);
}
} }
public Entry SecretEntry public Entry SecretEntry
@ -65,52 +74,60 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); try
_broadcasterService.Subscribe(nameof(LockPage), message =>
{ {
if (message.Command == Constants.ClearSensitiveFields) base.OnAppearing();
_broadcasterService.Subscribe(nameof(LockPage), message =>
{ {
MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields); if (message.Command == Constants.ClearSensitiveFields)
}
});
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); MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields);
MainThread.BeginInvokeOnMainThread(async () => await _vm.PromptBiometricAsync()); }
}); });
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) 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) private async void More_Clicked(object sender, System.EventArgs e)
{ {
await _accountListOverlay.HideAsync(); try
if (!DoOnce())
{ {
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();
}
} }
catch (Exception ex)
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.LogOut);
if (selection == AppResources.LogOut)
{ {
await _vm.LogOutAsync(); LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
} }

View file

@ -14,6 +14,7 @@ namespace Bit.App.Pages
{ {
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>(); private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>(); private readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
protected int ShowModalAnimationDelay = 400; protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100; protected int ShowPageAnimationDelay = 100;
@ -48,22 +49,38 @@ namespace Bit.App.Pages
protected override async void OnNavigatedTo(NavigatedToEventArgs args) protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{ {
base.OnNavigatedTo(args); try
if (IsThemeDirty)
{ {
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();
} }
catch (Exception ex)
await SaveActivityAsync();
if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo)
{ {
return; Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
_hasInitedOnNavigatedTo = true;
await InitOnNavigatedToAsync();
} }
protected virtual Task InitOnNavigatedToAsync() => Task.CompletedTask; protected virtual Task InitOnNavigatedToAsync() => Task.CompletedTask;
@ -116,29 +133,37 @@ namespace Bit.App.Pages
{ {
async Task DoWorkAsync() async Task DoWorkAsync()
{ {
await workFunction.Invoke(); try
if (sourceView != null)
{ {
if (targetView != null) await workFunction.Invoke();
if (sourceView != null)
{ {
targetView.Content = sourceView; if (targetView != null)
} {
else targetView.Content = sourceView;
{ }
Content = sourceView; else
{
Content = sourceView;
}
} }
} }
catch (Exception ex)
{
_logger.Value.Exception(ex);
throw;
}
} }
if (DeviceInfo.Platform == DevicePlatform.iOS)
{ #if IOS
await DoWorkAsync(); await DoWorkAsync();
return; #else
}
await Task.Run(async () => await Task.Run(async () =>
{ {
await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay); await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay);
MainThread.BeginInvokeOnMainThread(async () => await DoWorkAsync()); MainThread.BeginInvokeOnMainThread(async () => await DoWorkAsync());
}); });
#endif
} }
protected void RequestFocus(InputView input) protected void RequestFocus(InputView input)

View file

@ -42,7 +42,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
var history = await _passwordGenerationService.GetHistoryAsync(); var history = await _passwordGenerationService.GetHistoryAsync();
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>()); History.ResetWithRange(history ?? new List<GeneratedPasswordHistory>());
ShowNoData = History.Count == 0; ShowNoData = History.Count == 0;
@ -66,7 +66,7 @@ namespace Bit.App.Pages
{ {
try try
{ {
await Device.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List<GeneratedPasswordHistory>())); await MainThread.InvokeOnMainThreadAsync(() => History.ResetWithRange(new List<GeneratedPasswordHistory>()));
await InitAsync(); await InitAsync();
} }

View file

@ -15,6 +15,7 @@ namespace Bit.App.Pages
public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly ILogger _logger;
private GeneratorPageViewModel _vm; private GeneratorPageViewModel _vm;
private readonly bool _fromTabPage; private readonly bool _fromTabPage;
@ -26,6 +27,8 @@ namespace Bit.App.Pages
_tabsPage = tabsPage; _tabsPage = tabsPage;
InitializeComponent(); InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>(); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_logger = ServiceContainer.Resolve<ILogger>();
_vm = BindingContext as GeneratorPageViewModel; _vm = BindingContext as GeneratorPageViewModel;
_vm.Page = this; _vm.Page = this;
_fromTabPage = fromTabPage; _fromTabPage = fromTabPage;
@ -35,26 +38,21 @@ namespace Bit.App.Pages
_vm.EmailWebsite = emailWebsite; _vm.EmailWebsite = emailWebsite;
_vm.EditMode = editMode; _vm.EditMode = editMode;
_vm.IosExtension = appOptions?.IosExtension ?? false; _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 (selectAction != null)
{ {
if (isIos) #if IOS
{ ToolbarItems.Add(_closeItem);
ToolbarItems.Add(_closeItem); #endif
}
ToolbarItems.Add(_selectItem); ToolbarItems.Add(_selectItem);
} }
else else
{ {
if (isIos) #if IOS
{ ToolbarItems.Add(_moreItem);
ToolbarItems.Add(_moreItem); #else
} ToolbarItems.Add(_historyItem);
else #endif
{
ToolbarItems.Add(_historyItem);
}
} }
_typePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished); _typePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_passwordTypePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished); _passwordTypePicker.On<Microsoft.Maui.Controls.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
@ -71,22 +69,30 @@ namespace Bit.App.Pages
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
base.OnAppearing(); try
lblPassword.IsVisible = true;
if (!_fromTabPage)
{ {
await InitAsync(); base.OnAppearing();
}
_broadcasterService.Subscribe(nameof(GeneratorPage), (message) => lblPassword.IsVisible = true;
{
if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY) 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() protected override void OnDisappearing()
@ -100,27 +106,35 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed() 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 ANDROID
if (Device.RuntimePlatform == Device.Android && _tabsPage != null) if (_tabsPage != null)
{ {
_tabsPage.ResetToVaultPage(); _tabsPage.ResetToVaultPage();
return true; return true;
} }
#endif
return base.OnBackButtonPressed(); return base.OnBackButtonPressed();
} }
private async void More_Clicked(object sender, EventArgs e) 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, catch (Exception ex)
null, AppResources.PasswordHistory);
if (selection == AppResources.PasswordHistory)
{ {
var page = new GeneratorHistoryPage(); _logger.Exception(ex);
await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page));
} }
} }
@ -144,7 +158,7 @@ namespace Bit.App.Pages
{ {
await base.UpdateOnThemeChanged(); await base.UpdateOnThemeChanged();
await Device.InvokeOnMainThreadAsync(() => await MainThread.InvokeOnMainThreadAsync(() =>
{ {
if (_vm != null) if (_vm != null)
{ {

View file

@ -18,6 +18,7 @@ namespace Bit.App.Pages
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ISendService _sendService; private readonly ISendService _sendService;
private readonly ILogger _logger;
private readonly SendGroupingsPageViewModel _vm; private readonly SendGroupingsPageViewModel _vm;
private readonly string _pageName; private readonly string _pageName;
@ -33,6 +34,8 @@ namespace Bit.App.Pages
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_sendService = ServiceContainer.Resolve<ISendService>("sendService"); _sendService = ServiceContainer.Resolve<ISendService>("sendService");
_logger = ServiceContainer.Resolve<ILogger>();
_vm = BindingContext as SendGroupingsPageViewModel; _vm = BindingContext as SendGroupingsPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.MainPage = mainPage; _vm.MainPage = mainPage;
@ -43,85 +46,89 @@ namespace Bit.App.Pages
_vm.PageTitle = pageTitle; _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 IOS
if (Device.RuntimePlatform == Device.iOS) _absLayout.Children.Remove(_fab);
if (type == null)
{ {
_absLayout.Children.Remove(_fab); ToolbarItems.Add(_aboutIconItem);
if (type == null)
{
ToolbarItems.Add(_aboutIconItem);
}
ToolbarItems.Add(_addItem);
}
else
{
ToolbarItems.Add(_syncItem);
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_aboutTextItem);
} }
ToolbarItems.Add(_addItem);
#else
ToolbarItems.Add(_syncItem);
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_aboutTextItem);
#endif
} }
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); try
if (_syncService.SyncInProgress)
{ {
IsBusy = true; base.OnAppearing();
} if (_syncService.SyncInProgress)
_broadcasterService.Subscribe(_pageName, async (message) =>
{
try
{ {
if (message.Command == "syncStarted") IsBusy = true;
{
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();
}
});
}
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
await LoadOnAppearedAsync(_mainLayout, false, async () => _broadcasterService.Subscribe(_pageName, async (message) =>
{
if (!_syncService.SyncInProgress || (await _sendService.GetAllAsync()).Any())
{ {
try 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); LoggerHelper.LogEvenIfCantBeResolved(ex);
await _vm.LoadAsync();
} }
} });
else
{
await Task.Delay(5000);
if (!_vm.Loaded)
{
await _vm.LoadAsync();
}
}
AdjustToolbar(); await LoadOnAppearedAsync(_mainLayout, false, async () =>
await CheckAddRequest(); {
}, _mainContent); 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() protected override void OnDisappearing()

View file

@ -1,22 +1,11 @@
using System; using Bit.App.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities; 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 namespace Bit.App.Pages
{ {
public class SendGroupingsPageViewModel : BaseViewModel public class SendGroupingsPageViewModel : BaseViewModel
@ -117,28 +106,38 @@ namespace Bit.App.Pages
{ {
return; 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; _logger.Value.Exception(ex);
} throw;
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();
var groupedSends = new List<SendGroupingsPageListGroup>(); var groupedSends = new List<SendGroupingsPageListGroup>();
var page = Page as SendGroupingsPage; var page = Page as SendGroupingsPage;
@ -146,8 +145,11 @@ namespace Bit.App.Pages
{ {
await LoadDataAsync(); 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 #if IOS
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS; var uppercaseGroupNames = true;
#else
var uppercaseGroupNames = false;
#endif
if (MainPage) if (MainPage)
{ {
groupedSends.Add(new SendGroupingsPageListGroup( groupedSends.Add(new SendGroupingsPageListGroup(
@ -208,6 +210,11 @@ namespace Bit.App.Pages
} }
} }
} }
catch (Exception ex)
{
_logger.Value.Exception(ex);
throw;
}
finally finally
{ {
_doingLoad = false; _doingLoad = false;
@ -315,14 +322,22 @@ namespace Bit.App.Pages
private async void SendOptionsAsync(SendView send) private async void SendOptionsAsync(SendView send)
{ {
if ((Page as BaseContentPage).DoOnce()) try
{ {
var selection = await AppHelpers.SendListOptions(Page, send); if ((Page as BaseContentPage).DoOnce())
if (selection == AppResources.RemovePassword || selection == AppResources.Delete)
{ {
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;
}
} }
} }
} }

View file

@ -29,7 +29,7 @@ namespace Bit.App.Pages
{ {
if (message.Command == "selectSaveFileResult") if (message.Command == "selectSaveFileResult")
{ {
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
var data = message.Data as Tuple<string, string>; var data = message.Data as Tuple<string, string>;
if (data == null) if (data == null)

View file

@ -49,12 +49,10 @@ namespace Bit.App.Pages
private void UpdatePlaceholder() 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 ANDROID
if (Device.RuntimePlatform == Device.Android) MainThread.BeginInvokeOnMainThread(() =>
{ _emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark"));
MainThread.BeginInvokeOnMainThread(() => #endif
_emptyPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_login_requests" : "empty_login_requests_dark"));
}
} }
} }
} }

View file

@ -10,6 +10,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Services;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -87,21 +88,29 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); try
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
{ {
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() private async Task ForcePasswordResetIfNeededAsync()
@ -174,23 +183,31 @@ namespace Bit.App.Pages
protected override async void OnCurrentPageChanged() 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); _messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
if (navPage.RootPage is GroupingsPage) if (navPage.RootPage is GroupingsPage)
{ {
// Load something? // Load something?
} }
else if (navPage.RootPage is GeneratorPage genPage) else if (navPage.RootPage is GeneratorPage genPage)
{ {
await genPage.InitAsync(); await genPage.InitAsync();
}
} }
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
} }
public void OnPageReselected() public void OnPageReselected()

View file

@ -33,7 +33,7 @@ namespace Bit.App.Pages
{ {
if (message.Command == "selectFileResult") if (message.Command == "selectFileResult")
{ {
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
var data = message.Data as Tuple<byte[], string>; var data = message.Data as Tuple<byte[], string>;
_vm.FileData = data.Item1; _vm.FileData = data.Item1;

View file

@ -7,6 +7,7 @@ using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Bit.Core.Services;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -263,8 +264,16 @@ namespace Bit.App.Pages
{ {
MainThread.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await Navigation.PopModalAsync(); try
await _vm.UpdateTotpKeyAsync(key); {
await Navigation.PopModalAsync();
await _vm.UpdateTotpKeyAsync(key);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}); });
}); });

View file

@ -63,12 +63,12 @@ namespace Bit.App.Pages
{ {
if (message.Command == "syncStarted") if (message.Command == "syncStarted")
{ {
Device.BeginInvokeOnMainThread(() => IsBusy = true); MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
} }
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
{ {
await Task.Delay(500); await Task.Delay(500);
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
IsBusy = false; IsBusy = false;
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully")) if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
@ -83,7 +83,7 @@ namespace Bit.App.Pages
} }
else if (message.Command == "selectSaveFileResult") else if (message.Command == "selectSaveFileResult")
{ {
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
var data = message.Data as Tuple<string, string>; var data = message.Data as Tuple<string, string>;
if (data == null) if (data == null)

View file

@ -109,52 +109,59 @@ namespace Bit.App.Pages
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
Task.Run(async () => Task.Run(async () =>
{ {
List<CipherView> ciphers = null; try
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText);
if (searchable || shouldShowAllWhenEmpty)
{ {
if (timeout != null) List<CipherView> 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 (timeout != null)
} {
if (searchText != (Page as CiphersPage).SearchBar.Text await Task.Delay(timeout.Value);
&& }
!shouldShowAllWhenEmpty) if (searchText != (Page as CiphersPage).SearchBar.Text
{ &&
return; !shouldShowAllWhenEmpty)
} {
return;
}
previousCts?.Cancel(); previousCts?.Cancel();
try try
{
var vaultFilteredCiphers = await GetAllCiphersAsync();
if (!shouldShowAllWhenEmpty)
{ {
ciphers = await _searchService.SearchCiphersAsync(searchText, var vaultFilteredCiphers = await GetAllCiphersAsync();
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token); 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<CipherView>();
} }
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<CipherView>(); _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); }, cts.Token);
_searchCancellationTokenSource = cts; _searchCancellationTokenSource = cts;
} }

View file

@ -1,9 +1,9 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Resources.Localization;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -19,6 +19,7 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ILogger _logger;
private readonly GroupingsPageViewModel _vm; private readonly GroupingsPageViewModel _vm;
private readonly string _pageName; private readonly string _pageName;
@ -39,6 +40,8 @@ namespace Bit.App.Pages
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_logger = ServiceContainer.Resolve<ILogger>();
_vm = BindingContext as GroupingsPageViewModel; _vm = BindingContext as GroupingsPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.MainPage = mainPage; _vm.MainPage = mainPage;
@ -57,17 +60,14 @@ namespace Bit.App.Pages
_vm.VaultFilterDescription = vaultFilterSelection; _vm.VaultFilterDescription = vaultFilterSelection;
} }
if (DeviceInfo.Platform == DevicePlatform.iOS) #if IOS
{ _absLayout.Children.Remove(_fab);
_absLayout.Children.Remove(_fab); ToolbarItems.Add(_addItem);
ToolbarItems.Add(_addItem); #else
} ToolbarItems.Add(_syncItem);
else ToolbarItems.Add(_lockItem);
{ ToolbarItems.Add(_exitItem);
ToolbarItems.Add(_syncItem); #endif
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_exitItem);
}
if (deleted || showTotp) if (deleted || showTotp)
{ {
_absLayout.Children.Remove(_fab); _absLayout.Children.Remove(_fab);
@ -81,107 +81,115 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); try
if (_syncService.SyncInProgress)
{ {
IsBusy = true; base.OnAppearing();
} if (_syncService.SyncInProgress)
_accountAvatar?.OnAppearing();
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(_pageName, async (message) =>
{
try
{ {
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); if (message.Command == "syncStarted")
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
if (_vm.MainPage)
{ {
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(); MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
} }
MainThread.BeginInvokeOnMainThread(() => else if (message.Command == "syncCompleted")
{ {
IsBusy = false; await Task.Delay(500);
if (_vm.LoadedOnce) 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)
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (_previousPage == null)
{
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
{ {
try LoggerHelper.LogEvenIfCantBeResolved(ex);
{
await _vm.LoadAsync();
}
catch (Exception e) when (e.Message.Contains("No key."))
{
await Task.Delay(1000);
await _vm.LoadAsync();
}
} }
else });
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (_previousPage == null)
{ {
await Task.Delay(5000); if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
if (!_vm.Loaded)
{ {
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();
await ShowPreviousPageAsync(); AdjustToolbar();
AdjustToolbar(); }, _mainContent);
}, _mainContent);
if (!_vm.MainPage) 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))
{ {
await _stateService.SetPushInitialPromptShownAsync(true); return;
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
AppResources.OkGotIt);
} }
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)) _logger.Exception(ex);
{ throw;
await _pushNotificationService.RegisterAsync();
}
} }
} }
@ -195,14 +203,22 @@ namespace Bit.App.Pages
return false; return false;
} }
protected override async void OnDisappearing() protected override void OnDisappearing()
{ {
base.OnDisappearing(); try
IsBusy = false; {
_vm.StopCiphersTotpTick().FireAndForget(); base.OnDisappearing();
_broadcasterService.Unsubscribe(_pageName); IsBusy = false;
_vm.DisableRefreshing(); _vm.StopCiphersTotpTick().FireAndForget();
_accountAvatar?.OnDisappearing(); _broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();
}
catch (Exception ex)
{
_logger.Exception(ex);
throw;
}
} }
private async void RowSelected(object sender, SelectionChangedEventArgs e) 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) private async void Search_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync(); try
if (DoOnce())
{ {
var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted); await _accountListOverlay.HideAsync();
await Navigation.PushModalAsync(new NavigationPage(page)); 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) private async void Sync_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync(); try
await _vm.SyncAsync(); {
await _accountListOverlay.HideAsync();
await _vm.SyncAsync();
}
catch (Exception ex)
{
_logger.Exception(ex);
}
} }
private async void Lock_Clicked(object sender, EventArgs e) private async void Lock_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync(); try
await _vaultTimeoutService.LockAsync(true, true); {
await _accountListOverlay.HideAsync();
await _vaultTimeoutService.LockAsync(true, true);
}
catch (Exception ex)
{
_logger.Exception(ex);
}
} }
private async void Exit_Clicked(object sender, EventArgs e) private async void Exit_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync(); try
await _vm.ExitAsync(); {
await _accountListOverlay.HideAsync();
await _vm.ExitAsync();
}
catch (Exception ex)
{
_logger.Exception(ex);
}
} }
private async void AddButton_Clicked(object sender, EventArgs e) private async void AddButton_Clicked(object sender, EventArgs e)
{ {
var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android; try
await _accountListOverlay.HideAsync();
if (skipAction)
{ {
// Account list in the process of closing via tapping on invisible FAB, skip this attempt var skipAction = _accountListOverlay.IsVisible && DeviceInfo.Platform == DevicePlatform.Android;
return; 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()); _logger.Exception(ex);
await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View file

@ -166,41 +166,52 @@ namespace Bit.App.Pages
{ {
return; 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(); try
await InitVaultFilterAsync(MainPage);
if (MainPage)
{ {
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<GroupingsPageListGroup>(); var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage; var page = Page as GroupingsPage;
_websiteIconsEnabled = await _stateService.GetDisableFaviconAsync() != true;
try try
{ {
await LoadDataAsync(); await LoadDataAsync();
@ -307,11 +318,6 @@ namespace Bit.App.Pages
await MainThread.InvokeOnMainThreadAsync(() => 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); GroupedItems.ReplaceRange(items);
}); });
} }
@ -335,23 +341,22 @@ namespace Bit.App.Pages
await MainThread.InvokeOnMainThreadAsync(() => await MainThread.InvokeOnMainThreadAsync(() =>
{ {
if (groupedItems.Any()) 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<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{ {
GroupedItems.Clear(); GroupedItems.Clear();
return;
} }
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}); });
} }
} }
catch (Exception ex)
{
_logger.Exception(ex);
throw;
}
finally finally
{ {
_doingLoad = false; _doingLoad = false;

View file

@ -42,7 +42,7 @@ namespace Bit.App.Pages
{ {
var cipher = await _cipherService.GetAsync(CipherId); var cipher = await _cipherService.GetAsync(CipherId);
var decCipher = await cipher.DecryptAsync(); var decCipher = await cipher.DecryptAsync();
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>()); History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
ShowNoData = History.Count == 0; ShowNoData = History.Count == 0;

View file

@ -204,7 +204,18 @@ namespace Bit.iOS.Core.Controllers
var tasks = Task.Run(async () => var tasks = Task.Run(async () =>
{ {
await Task.Delay(500); await Task.Delay(500);
NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync()); NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{
try
{
await PromptBiometricAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
});
}); });
} }
} }

View file

@ -31,7 +31,7 @@ namespace Bit.iOS.Core.Services
var dictArr = new NSDictionary<NSString, NSObject>[1]; var dictArr = new NSDictionary<NSString, NSObject>[1];
dictArr[0] = new NSDictionary<NSString, NSObject>(new NSString(UTType.UTF8PlainText), new NSString(text)); dictArr[0] = new NSDictionary<NSString, NSObject>(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, LocalOnly = true,
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null