diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index 9b2bba4ba..3b909a998 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -1,10 +1,13 @@
-using Bit.App.Abstractions;
+using System.Threading.Tasks;
+using Android.App;
+using Bit.App.Abstractions;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
+ private ProgressDialog _progressDialog;
private Android.Widget.Toast _toast;
public void Toast(string text, bool longDuration = false)
@@ -31,5 +34,29 @@ namespace Bit.Droid.Services
}
return launchIntent != null;
}
+
+ public async Task ShowLoadingAsync(string text)
+ {
+ if(_progressDialog != null)
+ {
+ await HideLoadingAsync();
+ }
+ var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
+ _progressDialog = new ProgressDialog(activity);
+ _progressDialog.SetMessage(text);
+ _progressDialog.SetCancelable(false);
+ _progressDialog.Show();
+ }
+
+ public Task HideLoadingAsync()
+ {
+ if(_progressDialog != null)
+ {
+ _progressDialog.Dismiss();
+ _progressDialog.Dispose();
+ _progressDialog = null;
+ }
+ return Task.FromResult(0);
+ }
}
}
\ No newline at end of file
diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs
index 88e01a465..a2e6b30cc 100644
--- a/src/App/Abstractions/IDeviceActionService.cs
+++ b/src/App/Abstractions/IDeviceActionService.cs
@@ -1,8 +1,12 @@
-namespace Bit.App.Abstractions
+using System.Threading.Tasks;
+
+namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
void Toast(string text, bool longDuration = false);
bool LaunchApp(string appName);
+ Task ShowLoadingAsync(string text);
+ Task HideLoadingAsync();
}
}
\ No newline at end of file
diff --git a/src/iOS.Core/Views/Toast.cs b/src/iOS.Core/Views/Toast.cs
new file mode 100644
index 000000000..e076b32dc
--- /dev/null
+++ b/src/iOS.Core/Views/Toast.cs
@@ -0,0 +1,143 @@
+using Foundation;
+using System;
+using UIKit;
+
+namespace Bit.iOS.Core.Views
+{
+ public class Toast : UIView
+ {
+ private NSTimer _dismissTimer;
+ private NSLayoutConstraint _heightConstraint;
+ private NSLayoutConstraint _leftMarginConstraint;
+ private NSLayoutConstraint _rightMarginConstraint;
+ private NSLayoutConstraint _bottomMarginConstraint;
+
+ public Toast(string text)
+ : base(CoreGraphics.CGRect.FromLTRB(0, 0, 320, 38))
+ {
+ TranslatesAutoresizingMaskIntoConstraints = false;
+ BackgroundColor = UIColor.DarkGray.ColorWithAlpha(0.9f);
+ Layer.CornerRadius = 15;
+ Layer.MasksToBounds = true;
+
+ MessageLabel = new UILabel
+ {
+ TranslatesAutoresizingMaskIntoConstraints = false,
+ TextColor = UIColor.White,
+ Font = UIFont.SystemFontOfSize(14),
+ BackgroundColor = UIColor.Clear,
+ LineBreakMode = UILineBreakMode.WordWrap,
+ TextAlignment = UITextAlignment.Center,
+ Lines = 0,
+ Text = text
+ };
+
+ AddSubview(MessageLabel);
+
+ var hMessageConstraints = NSLayoutConstraint.FromVisualFormat("H:|-5-[messageLabel]-5-|", 0, new NSDictionary(),
+ NSDictionary.FromObjectsAndKeys(new NSObject[] { MessageLabel },
+ new NSObject[] { new NSString("messageLabel") })
+ );
+
+ var vMessageConstraints = NSLayoutConstraint.FromVisualFormat("V:|-0-[messageLabel]-0-|", 0, new NSDictionary(),
+ NSDictionary.FromObjectsAndKeys(new NSObject[] { MessageLabel },
+ new NSObject[] { new NSString("messageLabel") })
+ );
+
+ AddConstraints(hMessageConstraints);
+ AddConstraints(vMessageConstraints);
+
+ AddGestureRecognizer(new UITapGestureRecognizer(() => Dismiss(false)));
+ }
+
+ public bool Dismissed { get; set; }
+ public Action DismissCallback { get; set; }
+ public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(3);
+ public UILabel MessageLabel { get; set; }
+ public nfloat LeftMargin { get; set; } = 5;
+ public nfloat RightMargin { get; set; } = 5;
+ public nfloat BottomMargin { get; set; } = 5;
+ public nfloat Height { get; set; } = 38;
+
+ public void Show()
+ {
+ if(Superview != null)
+ {
+ return;
+ }
+
+ _dismissTimer = NSTimer.CreateScheduledTimer(Duration, x => Dismiss());
+ LayoutIfNeeded();
+
+ var localSuperView = UIApplication.SharedApplication.KeyWindow;
+ if(localSuperView != null)
+ {
+ localSuperView.AddSubview(this);
+
+ _heightConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Height,
+ NSLayoutRelation.GreaterThanOrEqual, null, NSLayoutAttribute.NoAttribute, 1, Height);
+
+ _leftMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Left, NSLayoutRelation.Equal,
+ localSuperView, NSLayoutAttribute.Left, 1, LeftMargin);
+
+ _rightMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Right, NSLayoutRelation.Equal,
+ localSuperView, NSLayoutAttribute.Right, 1, -RightMargin);
+
+ _bottomMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal,
+ localSuperView, NSLayoutAttribute.Bottom, 1, -BottomMargin);
+
+ // Avoid the "UIView-Encapsulated-Layout-Height" constraint conflicts
+ // http://stackoverflow.com/questions/25059443/what-is-nslayoutconstraint-uiview-encapsulated-layout-height-and-how-should-i
+ _leftMarginConstraint.Priority = 999;
+ _rightMarginConstraint.Priority = 999;
+
+ AddConstraint(_heightConstraint);
+ localSuperView.AddConstraint(_leftMarginConstraint);
+ localSuperView.AddConstraint(_rightMarginConstraint);
+ localSuperView.AddConstraint(_bottomMarginConstraint);
+
+ ShowWithAnimation();
+ }
+ else
+ {
+ Console.WriteLine("Toast needs a keyWindows to display.");
+ }
+ }
+
+ public void Dismiss(bool animated = true)
+ {
+ if(Dismissed)
+ {
+ return;
+ }
+
+ Dismissed = true;
+ _dismissTimer?.Invalidate();
+ _dismissTimer = null;
+
+ if(!animated)
+ {
+ RemoveFromSuperview();
+ DismissCallback?.Invoke();
+ return;
+ }
+
+ SetNeedsLayout();
+ Animate(0.3f, 0, UIViewAnimationOptions.CurveEaseIn, () => { Alpha = 0; }, () =>
+ {
+ RemoveFromSuperview();
+ DismissCallback?.Invoke();
+ });
+ }
+
+ private void ShowWithAnimation()
+ {
+ Alpha = 0;
+ SetNeedsLayout();
+ _bottomMarginConstraint.Constant = -BottomMargin;
+ _leftMarginConstraint.Constant = LeftMargin;
+ _rightMarginConstraint.Constant = -RightMargin;
+ AnimateNotify(0.3f, 0, 0.7f, 5f, UIViewAnimationOptions.CurveEaseInOut, () => { Alpha = 1; }, null);
+ }
+ }
+}
diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj
index a33f2f3e9..89667d36b 100644
--- a/src/iOS.Core/iOS.Core.csproj
+++ b/src/iOS.Core/iOS.Core.csproj
@@ -49,6 +49,7 @@
+
diff --git a/src/iOS/Services/DeviceActionService.cs b/src/iOS/Services/DeviceActionService.cs
new file mode 100644
index 000000000..dc3ee67b8
--- /dev/null
+++ b/src/iOS/Services/DeviceActionService.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.iOS.Core.Views;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+
+namespace Bit.iOS.Services
+{
+ public class DeviceActionService : IDeviceActionService
+ {
+ private Toast _toast;
+ private UIAlertController _progressAlert;
+
+ public bool LaunchApp(string appName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Toast(string text, bool longDuration = false)
+ {
+ if(!_toast?.Dismissed ?? false)
+ {
+ _toast.Dismiss(false);
+ }
+ _toast = new Toast(text)
+ {
+ Duration = TimeSpan.FromSeconds(longDuration ? 5 : 3)
+ };
+ if(TabBarVisible())
+ {
+ _toast.BottomMargin = 55;
+ }
+ _toast.Show();
+ _toast.DismissCallback = () =>
+ {
+ _toast?.Dispose();
+ _toast = null;
+ };
+ }
+
+ public Task ShowLoadingAsync(string text)
+ {
+ if(_progressAlert != null)
+ {
+ HideLoadingAsync().GetAwaiter().GetResult();
+ }
+
+ var result = new TaskCompletionSource();
+
+ var loadingIndicator = new UIActivityIndicatorView(new CGRect(10, 5, 50, 50));
+ loadingIndicator.HidesWhenStopped = true;
+ loadingIndicator.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray;
+ loadingIndicator.StartAnimating();
+
+ _progressAlert = UIAlertController.Create(null, text, UIAlertControllerStyle.Alert);
+ _progressAlert.View.TintColor = UIColor.Black;
+ _progressAlert.View.Add(loadingIndicator);
+
+ var vc = GetPresentedViewController();
+ vc?.PresentViewController(_progressAlert, false, () => result.TrySetResult(0));
+ return result.Task;
+ }
+
+ public Task HideLoadingAsync()
+ {
+ var result = new TaskCompletionSource();
+ if(_progressAlert == null)
+ {
+ result.TrySetResult(0);
+ }
+ _progressAlert.DismissViewController(false, () => result.TrySetResult(0));
+ _progressAlert.Dispose();
+ _progressAlert = null;
+ return result.Task;
+ }
+
+ private UIViewController GetPresentedViewController()
+ {
+ var window = UIApplication.SharedApplication.KeyWindow;
+ var vc = window.RootViewController;
+ while(vc.PresentedViewController != null)
+ {
+ vc = vc.PresentedViewController;
+ }
+ return vc;
+ }
+
+ private bool TabBarVisible()
+ {
+ var vc = GetPresentedViewController();
+ return vc != null && (vc is UITabBarController ||
+ (vc.ChildViewControllers?.Any(c => c is UITabBarController) ?? false));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj
index c1a5914e6..9edfa14ae 100644
--- a/src/iOS/iOS.csproj
+++ b/src/iOS/iOS.csproj
@@ -92,6 +92,7 @@
+
@@ -171,7 +172,5 @@
false
-
-
-
+
\ No newline at end of file