From 9e51c4652289a8453e1240ef2bc8e739f57a51e2 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 9 Apr 2019 23:24:03 -0400 Subject: [PATCH] mobile platform utils --- src/Android/Android.csproj | 4 + src/Android/MainApplication.cs | 6 + src/Android/Services/DeviceActionService.cs | 35 ++++ src/App/Abstractions/IDeviceActionService.cs | 8 + src/App/App.xaml.cs | 20 ++- src/App/Models/DialogDetails.cs | 12 ++ .../Services/MobilePlatformUtilsService.cs | 169 ++++++++++++++++++ src/Core/Enums/DeviceType.cs | 25 +-- src/Core/Enums/PaymentMethodType.cs | 10 +- src/Core/Enums/PlanType.cs | 11 +- src/Core/Enums/TransactionType.cs | 9 +- 11 files changed, 257 insertions(+), 52 deletions(-) create mode 100644 src/Android/Services/DeviceActionService.cs create mode 100644 src/App/Abstractions/IDeviceActionService.cs create mode 100644 src/App/Models/DialogDetails.cs create mode 100644 src/App/Services/MobilePlatformUtilsService.cs diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index d6a695e4c..99a728ad2 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -49,6 +49,9 @@ + + 2.1.0.4 + 1.8.5 @@ -76,6 +79,7 @@ + diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index 3774eee07..173fdbc9f 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -15,5 +15,11 @@ namespace Bit.Droid public MainApplication(IntPtr handle, JniHandleOwnership transer) : base(handle, transer) { } + + public override void OnCreate() + { + base.OnCreate(); + Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this); + } } } \ No newline at end of file diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs new file mode 100644 index 000000000..9b2bba4ba --- /dev/null +++ b/src/Android/Services/DeviceActionService.cs @@ -0,0 +1,35 @@ +using Bit.App.Abstractions; +using Plugin.CurrentActivity; + +namespace Bit.Droid.Services +{ + public class DeviceActionService : IDeviceActionService + { + private Android.Widget.Toast _toast; + + public void Toast(string text, bool longDuration = false) + { + if(_toast != null) + { + _toast.Cancel(); + _toast.Dispose(); + _toast = null; + } + _toast = Android.Widget.Toast.MakeText(CrossCurrentActivity.Current.Activity, text, + longDuration ? Android.Widget.ToastLength.Long : Android.Widget.ToastLength.Short); + _toast.Show(); + } + + public bool LaunchApp(string appName) + { + var activity = CrossCurrentActivity.Current.Activity; + appName = appName.Replace("androidapp://", string.Empty); + var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName); + if(launchIntent != null) + { + activity.StartActivity(launchIntent); + } + return launchIntent != null; + } + } +} \ No newline at end of file diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs new file mode 100644 index 000000000..88e01a465 --- /dev/null +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -0,0 +1,8 @@ +namespace Bit.App.Abstractions +{ + public interface IDeviceActionService + { + void Toast(string text, bool longDuration = false); + bool LaunchApp(string appName); + } +} \ No newline at end of file diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 87c491658..186155371 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -1,4 +1,5 @@ -using Bit.App.Pages; +using Bit.App.Models; +using Bit.App.Pages; using Bit.App.Utilities; using System; using System.Reflection; @@ -17,6 +18,23 @@ namespace Bit.App ThemeManager.SetTheme("light"); MainPage = new TabsPage(); + + MessagingCenter.Subscribe(Current, "ShowDialog", async (sender, details) => + { + var confirmed = true; + // TODO: ok text + var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? "Ok" : details.ConfirmText; + if(!string.IsNullOrWhiteSpace(details.CancelText)) + { + confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText, + details.CancelText); + } + else + { + await MainPage.DisplayAlert(details.Title, details.Text, details.ConfirmText); + } + MessagingCenter.Send(Current, "ShowDialogResolve", new Tuple(details.DialogId, confirmed)); + }); } protected override void OnStart() diff --git a/src/App/Models/DialogDetails.cs b/src/App/Models/DialogDetails.cs new file mode 100644 index 000000000..a143388d4 --- /dev/null +++ b/src/App/Models/DialogDetails.cs @@ -0,0 +1,12 @@ +namespace Bit.App.Models +{ + public class DialogDetails + { + public string Text { get; set; } + public string Title { get; set; } + public string ConfirmText { get; set; } + public string CancelText { get; set; } + public string Type { get; set; } + public int DialogId { get; set; } + } +} diff --git a/src/App/Services/MobilePlatformUtilsService.cs b/src/App/Services/MobilePlatformUtilsService.cs new file mode 100644 index 000000000..f45bc8a79 --- /dev/null +++ b/src/App/Services/MobilePlatformUtilsService.cs @@ -0,0 +1,169 @@ +using Bit.App.Abstractions; +using Bit.App.Models; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Bit.App.Services +{ + public class MobilePlatformUtilsService + { + private static readonly Random _random = new Random(); + + private const int DialogPromiseExpiration = 600000; // 10 minutes + + private readonly IDeviceActionService _deviceActionService; + private readonly Dictionary, DateTime>> _showDialogResolves = + new Dictionary, DateTime>>(); + + public MobilePlatformUtilsService(IDeviceActionService deviceActionService) + { + _deviceActionService = deviceActionService; + + MessagingCenter.Subscribe>( + Xamarin.Forms.Application.Current, "ShowDialogResolve", (sender, details) => + { + var dialogId = details.Item1; + var confirmed = details.Item2; + if(_showDialogResolves.ContainsKey(dialogId)) + { + var resolveObj = _showDialogResolves[dialogId].Item1; + resolveObj.TrySetResult(confirmed); + } + + // Clean up old tasks + var deleteIds = new HashSet(); + foreach(var item in _showDialogResolves) + { + var age = DateTime.UtcNow - item.Value.Item2; + if(age.TotalMilliseconds > DialogPromiseExpiration) + { + deleteIds.Add(item.Key); + } + } + foreach(var id in deleteIds) + { + _showDialogResolves.Remove(id); + } + }); + } + + public string IdentityClientId => "mobile"; + + public Core.Enums.DeviceType GetDevice() + { + return Device.RuntimePlatform == Device.iOS ? Core.Enums.DeviceType.iOS : Core.Enums.DeviceType.Android; + } + + public string GetDeviceString() + { + return DeviceInfo.Model; + } + + public bool IsViewOpen() + { + return false; + } + + public int? LockTimeout() + { + return null; + } + + public void LaunchUri(string uri, Dictionary options = null) + { + if(uri.StartsWith("http://") || uri.StartsWith("https://")) + { + Browser.OpenAsync(uri, BrowserLaunchMode.External); + } + else + { + var launched = false; + if(Device.RuntimePlatform == Device.Android && uri.StartsWith("androidapp://")) + { + launched = _deviceActionService.LaunchApp(uri); + } + if(!launched && (options?.ContainsKey("page") ?? false)) + { + (options["page"] as Page).DisplayAlert(null, "", ""); // TODO + } + } + } + + public void SaveFile() + { + // TODO + } + + public string GetApplicationVersion() + { + return AppInfo.VersionString; + } + + public bool SupportsU2f() + { + return false; + } + + public void ShowToast(string type, string title, string text, Dictionary options = null) + { + ShowToast(type, title, new string[] { text }, options); + } + + public void ShowToast(string type, string title, string[] text, Dictionary options = null) + { + if(text.Length > 0) + { + var longDuration = options != null && options.ContainsKey("longDuration") ? + (bool)options["longDuration"] : false; + _deviceActionService.Toast(text[0], longDuration); + } + } + + public Task ShowDialogAsync(string text, string title = null, string confirmText = null, + string cancelText = null, string type = null) + { + var dialogId = 0; + lock(_random) + { + dialogId = _random.Next(0, int.MaxValue); + } + MessagingCenter.Send(Xamarin.Forms.Application.Current, "ShowAlert", new DialogDetails + { + Text = text, + Title = title, + ConfirmText = confirmText, + CancelText = cancelText, + Type = type, + DialogId = dialogId + }); + var tcs = new TaskCompletionSource(); + _showDialogResolves.Add(dialogId, new Tuple, DateTime>(tcs, DateTime.UtcNow)); + return tcs.Task; + } + + public bool IsDev() + { + return Core.Utilities.CoreHelpers.InDebugMode(); + } + + public bool IsSelfHost() + { + return false; + } + + public async Task CopyToClipboardAsync(string text, Dictionary options = null) + { + var clearMs = options != null && options.ContainsKey("clearMs") ? (int?)options["clearMs"] : null; + await Clipboard.SetTextAsync(text); + // TODO: send message 'copiedToClipboard' + } + + public async Task ReadFromClipboardAsync(Dictionary options = null) + { + return await Clipboard.GetTextAsync(); + } + } +} diff --git a/src/Core/Enums/DeviceType.cs b/src/Core/Enums/DeviceType.cs index 53aa21c76..69b740756 100644 --- a/src/Core/Enums/DeviceType.cs +++ b/src/Core/Enums/DeviceType.cs @@ -1,50 +1,27 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum DeviceType : byte { - [Display(Name = "Android")] Android = 0, - [Display(Name = "iOS")] iOS = 1, - [Display(Name = "Chrome Extension")] ChromeExtension = 2, - [Display(Name = "Firefox Extension")] FirefoxExtension = 3, - [Display(Name = "Opera Extension")] OperaExtension = 4, - [Display(Name = "Edge Extension")] EdgeExtension = 5, - [Display(Name = "Windows")] WindowsDesktop = 6, - [Display(Name = "macOS")] MacOsDesktop = 7, - [Display(Name = "Linux")] LinuxDesktop = 8, - [Display(Name = "Chrome")] ChromeBrowser = 9, - [Display(Name = "Firefox")] FirefoxBrowser = 10, - [Display(Name = "Opera")] OperaBrowser = 11, - [Display(Name = "Edge")] EdgeBrowser = 12, - [Display(Name = "Internet Explorer")] IEBrowser = 13, - [Display(Name = "Unknown Browser")] UnknownBrowser = 14, - [Display(Name = "Android")] AndroidAmazon = 15, - [Display(Name = "UWP")] UWP = 16, - [Display(Name = "Safari")] SafariBrowser = 17, - [Display(Name = "Vivaldi")] VivaldiBrowser = 18, - [Display(Name = "Vivaldi Extension")] VivaldiExtension = 19, - [Display(Name = "Safari Extension")] SafariExtension = 20 } } diff --git a/src/Core/Enums/PaymentMethodType.cs b/src/Core/Enums/PaymentMethodType.cs index 07ce1a5d8..0821f211b 100644 --- a/src/Core/Enums/PaymentMethodType.cs +++ b/src/Core/Enums/PaymentMethodType.cs @@ -1,20 +1,12 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum PaymentMethodType : byte { - [Display(Name = "Card")] Card = 0, - [Display(Name = "Bank Account")] BankAccount = 1, - [Display(Name = "PayPal")] PayPal = 2, - [Display(Name = "BitPay")] BitPay = 3, - [Display(Name = "Credit")] Credit = 4, - [Display(Name = "Wire Transfer")] WireTransfer = 5, } } diff --git a/src/Core/Enums/PlanType.cs b/src/Core/Enums/PlanType.cs index df02b8bf7..4381ec511 100644 --- a/src/Core/Enums/PlanType.cs +++ b/src/Core/Enums/PlanType.cs @@ -1,22 +1,13 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum PlanType : byte { - [Display(Name = "Free")] Free = 0, - [Display(Name = "Families")] FamiliesAnnually = 1, - [Display(Name = "Teams (Monthly)")] TeamsMonthly = 2, - [Display(Name = "Teams (Annually)")] TeamsAnnually = 3, - [Display(Name = "Enterprise (Monthly)")] EnterpriseMonthly = 4, - [Display(Name = "Enterprise (Annually)")] EnterpriseAnnually = 5, - [Display(Name = "Custom")] Custom = 6 } } diff --git a/src/Core/Enums/TransactionType.cs b/src/Core/Enums/TransactionType.cs index 02556ae1d..45baa68c0 100644 --- a/src/Core/Enums/TransactionType.cs +++ b/src/Core/Enums/TransactionType.cs @@ -1,18 +1,11 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum TransactionType : byte { - [Display(Name = "Charge")] Charge = 0, - [Display(Name = "Credit")] Credit = 1, - [Display(Name = "Promotional Credit")] PromotionalCredit = 2, - [Display(Name = "Referral Credit")] ReferralCredit = 3, - [Display(Name = "Refund")] Refund = 4, } }