diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 0b8ca91a4..462afcaf9 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -191,6 +191,7 @@
+
diff --git a/src/Android/Resources/layout/progress_dialog_layout.xml b/src/Android/Resources/layout/progress_dialog_layout.xml
new file mode 100644
index 000000000..0515dc3e1
--- /dev/null
+++ b/src/Android/Resources/layout/progress_dialog_layout.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index 9bb004161..c83689c97 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -13,6 +13,7 @@ using Android.OS;
using Android.Provider;
using Android.Text;
using Android.Text.Method;
+using Android.Views;
using Android.Views.Autofill;
using Android.Views.InputMethods;
using Android.Webkit;
@@ -27,6 +28,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
+using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
@@ -37,7 +39,9 @@ namespace Bit.Droid.Services
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func _eventServiceFunc;
- private ProgressDialog _progressDialog;
+ private AlertDialog _progressDialog;
+ object _progressDialogLock = new object();
+
private bool _cameraPermissionsDenied;
private Toast _toast;
private string _userAgent;
@@ -108,22 +112,101 @@ namespace Bit.Droid.Services
{
await HideLoadingAsync();
}
- var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
- _progressDialog = new ProgressDialog(activity);
- _progressDialog.SetMessage(text);
- _progressDialog.SetCancelable(false);
+
+ var activity = CrossCurrentActivity.Current.Activity;
+ var inflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService);
+ var dialogView = inflater.Inflate(Resource.Layout.progress_dialog_layout, null);
+
+ var txtLoading = dialogView.FindViewById(Resource.Id.txtLoading);
+ txtLoading.Text = text;
+ txtLoading.SetTextColor(ThemeHelpers.TextColor);
+
+ _progressDialog = new AlertDialog.Builder(activity)
+ .SetView(dialogView)
+ .SetCancelable(false)
+ .Create();
_progressDialog.Show();
}
public Task HideLoadingAsync()
{
- if (_progressDialog != null)
+ // Based on https://github.com/redth-org/AndHUD/blob/master/AndHUD/AndHUD.cs
+ lock (_progressDialogLock)
{
- _progressDialog.Dismiss();
- _progressDialog.Dispose();
- _progressDialog = null;
+ if (_progressDialog is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ void actionDismiss()
+ {
+ try
+ {
+ if (IsAlive(_progressDialog) && IsAlive(_progressDialog.Window))
+ {
+ _progressDialog.Hide();
+ _progressDialog.Dismiss();
+ }
+ }
+ catch
+ {
+ // ignore
+ }
+
+ _progressDialog = null;
+ }
+
+ // First try the SynchronizationContext
+ if (Application.SynchronizationContext != null)
+ {
+ Application.SynchronizationContext.Send(state => actionDismiss(), null);
+ return Task.CompletedTask;
+ }
+
+ // Otherwise try OwnerActivity on dialog
+ var ownerActivity = _progressDialog?.OwnerActivity;
+ if (IsAlive(ownerActivity))
+ {
+ ownerActivity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ // Otherwise try get it from the Window Context
+ if (_progressDialog?.Window?.Context is Activity windowActivity && IsAlive(windowActivity))
+ {
+ windowActivity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ // Finally if all else fails, let's see if current activity is MainActivity
+ if (CrossCurrentActivity.Current.Activity is MainActivity activity && IsAlive(activity))
+ {
+ activity.RunOnUiThread(actionDismiss);
+ return Task.CompletedTask;
+ }
+
+ return Task.CompletedTask;
}
- return Task.FromResult(0);
+ }
+
+ bool IsAlive(Java.Lang.Object @object)
+ {
+ if (@object == null)
+ return false;
+
+ if (@object.Handle == IntPtr.Zero)
+ return false;
+
+ if (@object is Activity activity)
+ {
+ if (activity.IsFinishing)
+ return false;
+
+ if (activity.IsDestroyed)
+ return false;
+ }
+
+ return true;
}
public bool OpenFile(byte[] fileData, string id, string fileName)
diff --git a/src/Android/Utilities/ThemeHelpers.cs b/src/Android/Utilities/ThemeHelpers.cs
index 017985b71..b33aabc53 100644
--- a/src/Android/Utilities/ThemeHelpers.cs
+++ b/src/Android/Utilities/ThemeHelpers.cs
@@ -36,6 +36,10 @@ namespace Bit.Droid.Utilities
{
get => ThemeManager.GetResourceColor("SwitchThumbColor").ToAndroid();
}
+ public static Color TextColor
+ {
+ get => ThemeManager.GetResourceColor("TextColor").ToAndroid();
+ }
public static void SetAppearance(string theme, bool osDarkModeEnabled)
{
diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs
index 6499fb863..52d648b38 100644
--- a/src/App/Pages/Vault/AddEditPageViewModel.cs
+++ b/src/App/Pages/Vault/AddEditPageViewModel.cs
@@ -13,7 +13,9 @@ using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.Core;
using Xamarin.Forms;
-using View = Xamarin.Forms.View;
+#if !FDROID
+using Microsoft.AppCenter.Crashes;
+#endif
namespace Bit.App.Pages
{
@@ -494,9 +496,12 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
+
await _cipherService.SaveWithServerAsync(cipher);
Cipher.Id = cipher.Id;
+
await _deviceActionService.HideLoadingAsync();
+
_platformUtilsService.ShowToast("success", null,
EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id);
@@ -512,19 +517,30 @@ namespace Bit.App.Pages
{
ViewPage?.UpdateCipherId(this.Cipher.Id);
}
- await Page.Navigation.PopModalAsync();
+ // if the app is tombstoned then PopModalAsync would throw index out of bounds
+ if (Page.Navigation?.ModalStack?.Count > 0)
+ {
+ await Page.Navigation.PopModalAsync();
+ }
}
return true;
}
- catch (ApiException e)
+ catch (ApiException apiEx)
{
await _deviceActionService.HideLoadingAsync();
- if (e?.Error != null)
+ if (apiEx?.Error != null)
{
- await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
+ await _platformUtilsService.ShowDialogAsync(apiEx.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
+ catch(Exception genex)
+ {
+#if !FDROID
+ Crashes.TrackError(genex);
+#endif
+ await _deviceActionService.HideLoadingAsync();
+ }
return false;
}