2019-04-10 05:33:12 +02:00
|
|
|
|
using System;
|
2019-04-29 22:09:27 +02:00
|
|
|
|
using System.IO;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
using System.Linq;
|
2019-05-11 05:43:35 +02:00
|
|
|
|
using System.Net;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Bit.App.Abstractions;
|
2019-05-09 17:44:27 +02:00
|
|
|
|
using Bit.App.Resources;
|
2019-04-29 22:09:27 +02:00
|
|
|
|
using Bit.Core.Abstractions;
|
2019-04-19 15:11:17 +02:00
|
|
|
|
using Bit.Core.Enums;
|
2019-05-17 20:34:00 +02:00
|
|
|
|
using Bit.Core.Models.View;
|
2019-08-29 02:27:15 +02:00
|
|
|
|
using Bit.iOS.Core.Utilities;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
using Bit.iOS.Core.Views;
|
|
|
|
|
using CoreGraphics;
|
|
|
|
|
using Foundation;
|
2019-05-17 15:45:07 +02:00
|
|
|
|
using LocalAuthentication;
|
2019-05-11 05:43:35 +02:00
|
|
|
|
using MobileCoreServices;
|
|
|
|
|
using Photos;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
using UIKit;
|
2019-05-15 19:09:49 +02:00
|
|
|
|
using Xamarin.Forms;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
|
2019-06-27 19:58:08 +02:00
|
|
|
|
namespace Bit.iOS.Core.Services
|
2019-04-10 05:33:12 +02:00
|
|
|
|
{
|
|
|
|
|
public class DeviceActionService : IDeviceActionService
|
|
|
|
|
{
|
2019-04-29 22:09:27 +02:00
|
|
|
|
private readonly IStorageService _storageService;
|
2019-05-11 05:43:35 +02:00
|
|
|
|
private readonly IMessagingService _messagingService;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
private Toast _toast;
|
|
|
|
|
private UIAlertController _progressAlert;
|
2019-09-04 17:52:32 +02:00
|
|
|
|
private string _userAgent;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
|
2019-05-11 05:43:35 +02:00
|
|
|
|
public DeviceActionService(
|
|
|
|
|
IStorageService storageService,
|
|
|
|
|
IMessagingService messagingService)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
|
|
|
|
_storageService = storageService;
|
2019-05-11 05:43:35 +02:00
|
|
|
|
_messagingService = messagingService;
|
2019-04-29 22:09:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 17:52:32 +02:00
|
|
|
|
public string DeviceUserAgent
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (string.IsNullOrWhiteSpace(_userAgent))
|
2019-09-04 17:52:32 +02:00
|
|
|
|
{
|
|
|
|
|
_userAgent = $"Bitwarden_Mobile/{Xamarin.Essentials.AppInfo.VersionString} " +
|
|
|
|
|
$"(iOS {UIDevice.CurrentDevice.SystemVersion}; Model {UIDevice.CurrentDevice.Model})";
|
|
|
|
|
}
|
|
|
|
|
return _userAgent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-19 15:11:17 +02:00
|
|
|
|
public DeviceType DeviceType => DeviceType.iOS;
|
|
|
|
|
|
2019-04-10 05:33:12 +02:00
|
|
|
|
public bool LaunchApp(string appName)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Toast(string text, bool longDuration = false)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!_toast?.Dismissed ?? false)
|
2019-04-10 05:33:12 +02:00
|
|
|
|
{
|
|
|
|
|
_toast.Dismiss(false);
|
|
|
|
|
}
|
|
|
|
|
_toast = new Toast(text)
|
|
|
|
|
{
|
|
|
|
|
Duration = TimeSpan.FromSeconds(longDuration ? 5 : 3)
|
|
|
|
|
};
|
2021-05-13 20:36:20 +02:00
|
|
|
|
_toast.BottomMargin = 110;
|
|
|
|
|
_toast.LeftMargin = 20;
|
|
|
|
|
_toast.RightMargin = 20;
|
2019-04-10 05:33:12 +02:00
|
|
|
|
_toast.Show();
|
|
|
|
|
_toast.DismissCallback = () =>
|
|
|
|
|
{
|
|
|
|
|
_toast?.Dispose();
|
|
|
|
|
_toast = null;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task ShowLoadingAsync(string text)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (_progressAlert != null)
|
2019-04-10 05:33:12 +02:00
|
|
|
|
{
|
|
|
|
|
HideLoadingAsync().GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = new TaskCompletionSource<int>();
|
|
|
|
|
|
|
|
|
|
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<int>();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (_progressAlert == null)
|
2019-04-10 05:33:12 +02:00
|
|
|
|
{
|
|
|
|
|
result.TrySetResult(0);
|
|
|
|
|
}
|
|
|
|
|
_progressAlert.DismissViewController(false, () => result.TrySetResult(0));
|
|
|
|
|
_progressAlert.Dispose();
|
|
|
|
|
_progressAlert = null;
|
|
|
|
|
return result.Task;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-29 22:09:27 +02:00
|
|
|
|
public bool OpenFile(byte[] fileData, string id, string fileName)
|
|
|
|
|
{
|
|
|
|
|
var filePath = Path.Combine(GetTempPath(), fileName);
|
|
|
|
|
File.WriteAllBytes(filePath, fileData);
|
|
|
|
|
var url = NSUrl.FromFilename(filePath);
|
|
|
|
|
var viewer = UIDocumentInteractionController.FromUrl(url);
|
|
|
|
|
var controller = GetVisibleViewController();
|
|
|
|
|
var rect = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ?
|
|
|
|
|
new CGRect(100, 5, 320, 320) : controller.View.Frame;
|
|
|
|
|
return viewer.PresentOpenInMenu(rect, controller.View, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool CanOpenFile(string fileName)
|
|
|
|
|
{
|
|
|
|
|
// Not sure of a way to check this ahead of time on iOS
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 22:10:58 +01:00
|
|
|
|
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
|
|
|
|
{
|
|
|
|
|
// OpenFile behavior is appropriate here as iOS prompts to save file
|
|
|
|
|
return OpenFile(fileData, id, fileName);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-29 22:09:27 +02:00
|
|
|
|
public async Task ClearCacheAsync()
|
|
|
|
|
{
|
|
|
|
|
var url = new NSUrl(GetTempPath());
|
|
|
|
|
var tmpFiles = NSFileManager.DefaultManager.GetDirectoryContent(url, null,
|
|
|
|
|
NSDirectoryEnumerationOptions.SkipsHiddenFiles, out NSError error);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (error == null && tmpFiles.Length > 0)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var item in tmpFiles)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
|
|
|
|
NSFileManager.DefaultManager.Remove(item, out NSError itemError);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-27 19:58:08 +02:00
|
|
|
|
await _storageService.SaveAsync(Bit.Core.Constants.LastFileCacheClearKey, DateTime.UtcNow);
|
2019-04-29 22:09:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 05:43:35 +02:00
|
|
|
|
public Task SelectFileAsync()
|
|
|
|
|
{
|
|
|
|
|
var controller = GetVisibleViewController();
|
|
|
|
|
var picker = new UIDocumentMenuViewController(new string[] { UTType.Data }, UIDocumentPickerMode.Import);
|
|
|
|
|
picker.AddOption(AppResources.Camera, UIImage.FromBundle("camera"), UIDocumentMenuOrder.First, () =>
|
|
|
|
|
{
|
|
|
|
|
var imagePicker = new UIImagePickerController
|
|
|
|
|
{
|
|
|
|
|
SourceType = UIImagePickerControllerSourceType.Camera
|
|
|
|
|
};
|
|
|
|
|
imagePicker.FinishedPickingMedia += ImagePicker_FinishedPickingMedia;
|
|
|
|
|
imagePicker.Canceled += ImagePicker_Canceled;
|
|
|
|
|
controller.PresentModalViewController(imagePicker, true);
|
|
|
|
|
});
|
|
|
|
|
picker.AddOption(AppResources.Photos, UIImage.FromBundle("photo"), UIDocumentMenuOrder.First, () =>
|
|
|
|
|
{
|
|
|
|
|
var imagePicker = new UIImagePickerController
|
|
|
|
|
{
|
|
|
|
|
SourceType = UIImagePickerControllerSourceType.PhotoLibrary
|
|
|
|
|
};
|
|
|
|
|
imagePicker.FinishedPickingMedia += ImagePicker_FinishedPickingMedia;
|
|
|
|
|
imagePicker.Canceled += ImagePicker_Canceled;
|
|
|
|
|
controller.PresentModalViewController(imagePicker, true);
|
|
|
|
|
});
|
|
|
|
|
picker.DidPickDocumentPicker += (sender, e) =>
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (SystemMajorVersion() < 11)
|
2019-06-25 23:46:37 +02:00
|
|
|
|
{
|
|
|
|
|
e.DocumentPicker.DidPickDocument += DocumentPicker_DidPickDocument;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
e.DocumentPicker.Delegate = new PickerDelegate(this);
|
|
|
|
|
}
|
2019-05-11 05:43:35 +02:00
|
|
|
|
controller.PresentViewController(e.DocumentPicker, true, null);
|
|
|
|
|
};
|
|
|
|
|
var root = UIApplication.SharedApplication.KeyWindow.RootViewController;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (picker.PopoverPresentationController != null && root != null)
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
picker.PopoverPresentationController.SourceView = root.View;
|
|
|
|
|
picker.PopoverPresentationController.SourceRect = root.View.Bounds;
|
|
|
|
|
}
|
|
|
|
|
controller.PresentViewController(picker, true, null);
|
|
|
|
|
return Task.FromResult(0);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-09 17:44:27 +02:00
|
|
|
|
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
2019-05-16 21:54:21 +02:00
|
|
|
|
string text = null, string okButtonText = null, string cancelButtonText = null,
|
2021-05-21 15:13:54 +02:00
|
|
|
|
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
2019-05-09 17:44:27 +02:00
|
|
|
|
{
|
|
|
|
|
var result = new TaskCompletionSource<string>();
|
|
|
|
|
var alert = UIAlertController.Create(title ?? string.Empty, description, UIAlertControllerStyle.Alert);
|
|
|
|
|
UITextField input = null;
|
|
|
|
|
okButtonText = okButtonText ?? AppResources.Ok;
|
|
|
|
|
cancelButtonText = cancelButtonText ?? AppResources.Cancel;
|
|
|
|
|
alert.AddAction(UIAlertAction.Create(cancelButtonText, UIAlertActionStyle.Cancel, x =>
|
|
|
|
|
{
|
|
|
|
|
result.TrySetResult(null);
|
|
|
|
|
}));
|
|
|
|
|
alert.AddAction(UIAlertAction.Create(okButtonText, UIAlertActionStyle.Default, x =>
|
|
|
|
|
{
|
|
|
|
|
result.TrySetResult(input.Text ?? string.Empty);
|
|
|
|
|
}));
|
|
|
|
|
alert.AddTextField(x =>
|
|
|
|
|
{
|
|
|
|
|
input = x;
|
|
|
|
|
input.Text = text ?? string.Empty;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (numericKeyboard)
|
2019-05-16 21:54:21 +02:00
|
|
|
|
{
|
|
|
|
|
input.KeyboardType = UIKeyboardType.NumberPad;
|
|
|
|
|
}
|
2021-05-21 15:13:54 +02:00
|
|
|
|
if (password) {
|
|
|
|
|
input.SecureTextEntry = true;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!ThemeHelpers.LightTheme)
|
2019-08-29 02:27:15 +02:00
|
|
|
|
{
|
|
|
|
|
input.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
|
|
|
|
}
|
2019-05-09 17:44:27 +02:00
|
|
|
|
});
|
|
|
|
|
var vc = GetPresentedViewController();
|
|
|
|
|
vc?.PresentViewController(alert, true, null);
|
|
|
|
|
return result.Task;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 19:09:49 +02:00
|
|
|
|
public void RateApp()
|
|
|
|
|
{
|
|
|
|
|
string uri = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (SystemMajorVersion() < 11)
|
2019-05-15 19:09:49 +02:00
|
|
|
|
{
|
|
|
|
|
uri = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews" +
|
|
|
|
|
"?id=1137397744&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
uri = "itms-apps://itunes.apple.com/us/app/id1137397744?action=write-review";
|
|
|
|
|
}
|
|
|
|
|
Device.OpenUri(new Uri(uri));
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 15:11:48 +02:00
|
|
|
|
public bool SupportsFaceBiometric()
|
2019-05-17 15:45:07 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (SystemMajorVersion() < 11)
|
2019-05-17 15:45:07 +02:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
using (var context = new LAContext())
|
2019-10-23 15:11:48 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out var e))
|
2019-10-23 15:11:48 +02:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return context.BiometryType == LABiometryType.FaceId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<bool> SupportsFaceBiometricAsync()
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(SupportsFaceBiometric());
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 18:03:35 +02:00
|
|
|
|
public bool SupportsNfc()
|
|
|
|
|
{
|
2020-05-29 21:25:06 +02:00
|
|
|
|
if(Application.Current is App.App currentApp && !currentApp.Options.IosExtension)
|
|
|
|
|
{
|
|
|
|
|
return CoreNFC.NFCNdefReaderSession.ReadingAvailable;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2019-05-17 18:03:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsCamera()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsAutofillService()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 19:46:32 +02:00
|
|
|
|
public int SystemMajorVersion()
|
|
|
|
|
{
|
|
|
|
|
var versionParts = UIDevice.CurrentDevice.SystemVersion.Split('.');
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (versionParts.Length > 0 && int.TryParse(versionParts[0], out var version))
|
2019-05-17 19:46:32 +02:00
|
|
|
|
{
|
|
|
|
|
return version;
|
|
|
|
|
}
|
|
|
|
|
// unable to determine version
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string SystemModel()
|
|
|
|
|
{
|
|
|
|
|
return UIDevice.CurrentDevice.Model;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 20:04:16 +02:00
|
|
|
|
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
|
|
|
|
|
{
|
|
|
|
|
var result = new TaskCompletionSource<string>();
|
|
|
|
|
var alert = UIAlertController.Create(title ?? string.Empty, message, UIAlertControllerStyle.Alert);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(cancel))
|
2019-05-17 20:04:16 +02:00
|
|
|
|
{
|
|
|
|
|
alert.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel, x =>
|
|
|
|
|
{
|
|
|
|
|
result.TrySetResult(cancel);
|
|
|
|
|
}));
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var button in buttons)
|
2019-05-17 20:04:16 +02:00
|
|
|
|
{
|
|
|
|
|
alert.AddAction(UIAlertAction.Create(button, UIAlertActionStyle.Default, x =>
|
|
|
|
|
{
|
|
|
|
|
result.TrySetResult(button);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
var vc = GetPresentedViewController();
|
|
|
|
|
vc?.PresentViewController(alert, true, null);
|
|
|
|
|
return result.Task;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-29 18:26:36 +02:00
|
|
|
|
public Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction,
|
|
|
|
|
params string[] buttons)
|
|
|
|
|
{
|
2020-05-29 21:25:06 +02:00
|
|
|
|
if (Application.Current is App.App app && app.Options != null && !app.Options.IosExtension)
|
2020-05-29 18:26:36 +02:00
|
|
|
|
{
|
|
|
|
|
return app.MainPage.DisplayActionSheet(title, cancel, destruction, buttons);
|
|
|
|
|
}
|
|
|
|
|
var result = new TaskCompletionSource<string>();
|
|
|
|
|
var vc = GetPresentedViewController();
|
|
|
|
|
var sheet = UIAlertController.Create(title, null, UIAlertControllerStyle.ActionSheet);
|
|
|
|
|
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
|
|
|
|
{
|
|
|
|
|
var x = vc.View.Bounds.Width / 2;
|
|
|
|
|
var y = vc.View.Bounds.Bottom;
|
|
|
|
|
var rect = new CGRect(x, y, 0, 0);
|
|
|
|
|
|
|
|
|
|
sheet.PopoverPresentationController.SourceView = vc.View;
|
|
|
|
|
sheet.PopoverPresentationController.SourceRect = rect;
|
|
|
|
|
sheet.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Unknown;
|
|
|
|
|
}
|
|
|
|
|
foreach (var button in buttons)
|
|
|
|
|
{
|
|
|
|
|
sheet.AddAction(UIAlertAction.Create(button, UIAlertActionStyle.Default,
|
|
|
|
|
x => result.TrySetResult(button)));
|
|
|
|
|
}
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(destruction))
|
|
|
|
|
{
|
|
|
|
|
sheet.AddAction(UIAlertAction.Create(destruction, UIAlertActionStyle.Destructive,
|
|
|
|
|
x => result.TrySetResult(destruction)));
|
|
|
|
|
}
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(cancel))
|
|
|
|
|
{
|
|
|
|
|
sheet.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel,
|
|
|
|
|
x => result.TrySetResult(cancel)));
|
|
|
|
|
}
|
|
|
|
|
vc.PresentViewController(sheet, true, null);
|
|
|
|
|
return result.Task;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 20:34:00 +02:00
|
|
|
|
public void Autofill(CipherView cipher)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CloseAutofill()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Background()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 20:46:11 +02:00
|
|
|
|
public bool AutofillAccessibilityServiceRunning()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool AutofillServiceEnabled()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 15:37:57 +01:00
|
|
|
|
public void DisableAutofillService()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool AutofillServicesEnabled()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 20:46:11 +02:00
|
|
|
|
public string GetBuildNumber()
|
|
|
|
|
{
|
|
|
|
|
return NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OpenAccessibilitySettings()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OpenAutofillSettings()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-30 22:41:31 +02:00
|
|
|
|
public bool UsingDarkTheme()
|
|
|
|
|
{
|
2019-10-04 15:32:59 +02:00
|
|
|
|
try
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (SystemMajorVersion() > 12)
|
2019-10-04 15:32:59 +02:00
|
|
|
|
{
|
|
|
|
|
return UIScreen.MainScreen.TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
return false;
|
2019-09-30 22:41:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 21:29:30 +01:00
|
|
|
|
public long GetActiveTime()
|
|
|
|
|
{
|
2021-01-15 20:04:07 +01:00
|
|
|
|
// Fall back to UnixTimeMilliseconds in case this approach stops working. We'll lose clock-change
|
|
|
|
|
// protection but the lock functionality will continue to work.
|
|
|
|
|
return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
2020-12-14 21:29:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-31 16:19:05 +02:00
|
|
|
|
public void CloseMainApp()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 05:43:35 +02:00
|
|
|
|
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (sender is UIImagePickerController picker)
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
string fileName = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (e.Info.TryGetValue(UIImagePickerController.ReferenceUrl, out NSObject urlObj))
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
var result = PHAsset.FetchAssets(new NSUrl[] { (urlObj as NSUrl) }, null);
|
|
|
|
|
fileName = result?.firstObject?.ValueForKey(new NSString("filename"))?.ToString();
|
|
|
|
|
}
|
|
|
|
|
fileName = fileName ?? $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
|
|
|
|
var lowerFilename = fileName?.ToLowerInvariant();
|
|
|
|
|
byte[] data;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (lowerFilename != null && (lowerFilename.EndsWith(".jpg") || lowerFilename.EndsWith(".jpeg")))
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
using (var imageData = e.OriginalImage.AsJPEG())
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
data = new byte[imageData.Length];
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, data, 0,
|
|
|
|
|
Convert.ToInt32(imageData.Length));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
using (var imageData = e.OriginalImage.AsPNG())
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
data = new byte[imageData.Length];
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, data, 0,
|
|
|
|
|
Convert.ToInt32(imageData.Length));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SelectFileResult(data, fileName);
|
|
|
|
|
picker.DismissViewController(true, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ImagePicker_Canceled(object sender, EventArgs e)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (sender is UIImagePickerController picker)
|
2019-05-11 05:43:35 +02:00
|
|
|
|
{
|
|
|
|
|
picker.DismissViewController(true, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DocumentPicker_DidPickDocument(object sender, UIDocumentPickedEventArgs e)
|
|
|
|
|
{
|
2019-06-25 23:46:37 +02:00
|
|
|
|
PickedDocument(e.Url);
|
2019-05-11 05:43:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SelectFileResult(byte[] data, string fileName)
|
|
|
|
|
{
|
|
|
|
|
_messagingService.Send("selectFileResult", new Tuple<byte[], string>(data, fileName));
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-29 22:09:27 +02:00
|
|
|
|
private UIViewController GetVisibleViewController(UIViewController controller = null)
|
|
|
|
|
{
|
|
|
|
|
controller = controller ?? UIApplication.SharedApplication.KeyWindow.RootViewController;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (controller.PresentedViewController == null)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
|
|
|
|
return controller;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (controller.PresentedViewController is UINavigationController)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
|
|
|
|
return ((UINavigationController)controller.PresentedViewController).VisibleViewController;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (controller.PresentedViewController is UITabBarController)
|
2019-04-29 22:09:27 +02:00
|
|
|
|
{
|
|
|
|
|
return ((UITabBarController)controller.PresentedViewController).SelectedViewController;
|
|
|
|
|
}
|
|
|
|
|
return GetVisibleViewController(controller.PresentedViewController);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-10 05:33:12 +02:00
|
|
|
|
private UIViewController GetPresentedViewController()
|
|
|
|
|
{
|
|
|
|
|
var window = UIApplication.SharedApplication.KeyWindow;
|
|
|
|
|
var vc = window.RootViewController;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
while (vc.PresentedViewController != null)
|
2019-04-10 05:33:12 +02:00
|
|
|
|
{
|
|
|
|
|
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));
|
|
|
|
|
}
|
2019-05-09 17:44:27 +02:00
|
|
|
|
|
2019-04-29 22:09:27 +02:00
|
|
|
|
// ref: //https://developer.xamarin.com/guides/ios/application_fundamentals/working_with_the_file_system/
|
|
|
|
|
public string GetTempPath()
|
|
|
|
|
{
|
|
|
|
|
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
|
|
|
|
return Path.Combine(documents, "..", "tmp");
|
|
|
|
|
}
|
2019-06-25 23:46:37 +02:00
|
|
|
|
|
|
|
|
|
public void PickedDocument(NSUrl url)
|
|
|
|
|
{
|
|
|
|
|
url.StartAccessingSecurityScopedResource();
|
|
|
|
|
var doc = new UIDocument(url);
|
|
|
|
|
var fileName = doc.LocalizedName;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
2019-06-25 23:46:37 +02:00
|
|
|
|
{
|
|
|
|
|
var path = doc.FileUrl?.ToString();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (path != null)
|
2019-06-25 23:46:37 +02:00
|
|
|
|
{
|
|
|
|
|
path = WebUtility.UrlDecode(path);
|
|
|
|
|
var split = path.LastIndexOf('/');
|
|
|
|
|
fileName = path.Substring(split + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var fileCoordinator = new NSFileCoordinator();
|
|
|
|
|
fileCoordinator.CoordinateRead(url, NSFileCoordinatorReadingOptions.WithoutChanges,
|
|
|
|
|
out NSError error, (u) =>
|
|
|
|
|
{
|
|
|
|
|
var data = NSData.FromUrl(u).ToArray();
|
|
|
|
|
SelectFileResult(data, fileName ?? "unknown_file_name");
|
|
|
|
|
});
|
|
|
|
|
url.StopAccessingSecurityScopedResource();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-10 16:20:19 +01:00
|
|
|
|
public bool AutofillAccessibilityOverlayPermitted()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OpenAccessibilityOverlayPermissionSettings()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-25 23:46:37 +02:00
|
|
|
|
public class PickerDelegate : UIDocumentPickerDelegate
|
|
|
|
|
{
|
|
|
|
|
private readonly DeviceActionService _deviceActionService;
|
|
|
|
|
|
|
|
|
|
public PickerDelegate(DeviceActionService deviceActionService)
|
|
|
|
|
{
|
|
|
|
|
_deviceActionService = deviceActionService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void DidPickDocument(UIDocumentPickerViewController controller, NSUrl url)
|
|
|
|
|
{
|
|
|
|
|
_deviceActionService.PickedDocument(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-10 05:33:12 +02:00
|
|
|
|
}
|
2019-08-29 02:27:15 +02:00
|
|
|
|
}
|