diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 2b4256edc..7725cb088 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -100,6 +100,7 @@
+
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index 38f1506bb..fba508cb6 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -2,6 +2,15 @@
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
+using Bit.Core;
+using System.Linq;
+using Bit.App.Abstractions;
+using Bit.Core.Utilities;
+using Bit.Core.Abstractions;
+using System.IO;
+using System;
+using Android.Content;
+using Bit.Droid.Utilities;
namespace Bit.Droid
{
@@ -14,8 +23,14 @@ namespace Bit.Droid
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
+ private IDeviceActionService _deviceActionService;
+ private IMessagingService _messagingService;
+
protected override void OnCreate(Bundle savedInstanceState)
{
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _messagingService = ServiceContainer.Resolve("messagingService");
+
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
@@ -25,11 +40,63 @@ namespace Bit.Droid
LoadApplication(new App.App());
}
- public override void OnRequestPermissionsResult(int requestCode, string[] permissions,
+ public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Permission[] grantResults)
{
- Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ if(requestCode == Constants.SelectFilePermissionRequestCode)
+ {
+ if(grantResults.Any(r => r != Permission.Granted))
+ {
+ _messagingService.Send("selectFileCameraPermissionDenied");
+ }
+ await _deviceActionService.SelectFileAsync();
+ }
+ else
+ {
+ Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
+
+ protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
+ {
+ if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
+ {
+ Android.Net.Uri uri = null;
+ string fileName = null;
+ if(data != null && data.Data != null)
+ {
+ uri = data.Data;
+ fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
+ }
+ else
+ {
+ // camera
+ var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
+ var file = new Java.IO.File(root, "temp_camera_photo.jpg");
+ uri = Android.Net.Uri.FromFile(file);
+ fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
+ }
+
+ if(uri == null)
+ {
+ return;
+ }
+ try
+ {
+ using(var stream = ContentResolver.OpenInputStream(uri))
+ using(var memoryStream = new MemoryStream())
+ {
+ stream.CopyTo(memoryStream);
+ _messagingService.Send("selectFileResult",
+ new Tuple(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
+ }
+ }
+ catch(Java.IO.FileNotFoundException)
+ {
+ return;
+ }
+ }
+ }
}
}
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index df6568a51..fab8d5a82 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -54,7 +54,8 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
- var deviceActionService = new DeviceActionService(mobileStorageService);
+ var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
+ broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index 49b74f50d..47d1b6bef 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -1,9 +1,14 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
+using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
+using Android.OS;
+using Android.Provider;
+using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Webkit;
using Android.Widget;
@@ -19,13 +24,28 @@ namespace Bit.Droid.Services
public class DeviceActionService : IDeviceActionService
{
private readonly IStorageService _storageService;
-
+ private readonly IMessagingService _messagingService;
+ private readonly IBroadcasterService _broadcasterService;
private ProgressDialog _progressDialog;
- private Android.Widget.Toast _toast;
+ private bool _cameraPermissionsDenied;
+ private Toast _toast;
- public DeviceActionService(IStorageService storageService)
+ public DeviceActionService(
+ IStorageService storageService,
+ IMessagingService messagingService,
+ IBroadcasterService broadcasterService)
{
_storageService = storageService;
+ _messagingService = messagingService;
+ _broadcasterService = broadcasterService;
+
+ _broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
+ {
+ if(message.Command == "selectFileCameraPermissionDenied")
+ {
+ _cameraPermissionsDenied = true;
+ }
+ });
}
public DeviceType DeviceType => DeviceType.Android;
@@ -39,7 +59,7 @@ namespace Bit.Droid.Services
_toast = null;
}
_toast = Android.Widget.Toast.MakeText(CrossCurrentActivity.Current.Activity, text,
- longDuration ? Android.Widget.ToastLength.Long : Android.Widget.ToastLength.Short);
+ longDuration ? ToastLength.Long : ToastLength.Short);
_toast.Show();
}
@@ -149,6 +169,54 @@ namespace Bit.Droid.Services
catch(Exception) { }
}
+ public Task SelectFileAsync()
+ {
+ var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
+ var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage);
+ var additionalIntents = new List();
+ if(activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
+ {
+ var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
+ if(!_cameraPermissionsDenied && !hasStorageWritePermission)
+ {
+ AskPermission(Manifest.Permission.WriteExternalStorage);
+ return Task.FromResult(0);
+ }
+ if(!_cameraPermissionsDenied && !hasCameraPermission)
+ {
+ AskPermission(Manifest.Permission.Camera);
+ return Task.FromResult(0);
+ }
+ if(!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
+ {
+ try
+ {
+ var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
+ var file = new Java.IO.File(root, "temp_camera_photo.jpg");
+ if(!file.Exists())
+ {
+ file.ParentFile.Mkdirs();
+ file.CreateNewFile();
+ }
+ var outputFileUri = Android.Net.Uri.FromFile(file);
+ additionalIntents.AddRange(GetCameraIntents(outputFileUri));
+ }
+ catch(Java.IO.IOException) { }
+ }
+ }
+
+ var docIntent = new Intent(Intent.ActionOpenDocument);
+ docIntent.AddCategory(Intent.CategoryOpenable);
+ docIntent.SetType("*/*");
+ var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
+ if(additionalIntents.Count > 0)
+ {
+ chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
+ }
+ activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
+ return Task.FromResult(0);
+ }
+
public Task DisplayPromptAync(string title = null, string description = null,
string text = null, string okButtonText = null, string cancelButtonText = null)
{
@@ -217,5 +285,35 @@ namespace Bit.Droid.Services
return false;
}
}
+
+ private bool HasPermission(string permission)
+ {
+ return ContextCompat.CheckSelfPermission(
+ CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
+ }
+
+ private void AskPermission(string permission)
+ {
+ ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
+ Constants.SelectFilePermissionRequestCode);
+ }
+
+ private List GetCameraIntents(Android.Net.Uri outputUri)
+ {
+ var intents = new List();
+ var pm = CrossCurrentActivity.Current.Activity.PackageManager;
+ var captureIntent = new Intent(MediaStore.ActionImageCapture);
+ var listCam = pm.QueryIntentActivities(captureIntent, 0);
+ foreach(var res in listCam)
+ {
+ var packageName = res.ActivityInfo.PackageName;
+ var intent = new Intent(captureIntent);
+ intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
+ intent.SetPackage(packageName);
+ intent.PutExtra(MediaStore.ExtraOutput, outputUri);
+ intents.Add(intent);
+ }
+ return intents;
+ }
}
}
\ No newline at end of file
diff --git a/src/Android/Utilities/AndroidHelpers.cs b/src/Android/Utilities/AndroidHelpers.cs
new file mode 100644
index 000000000..5973ca5e8
--- /dev/null
+++ b/src/Android/Utilities/AndroidHelpers.cs
@@ -0,0 +1,30 @@
+using Android.Content;
+using Android.Provider;
+
+namespace Bit.Droid.Utilities
+{
+ public static class AndroidHelpers
+ {
+ public static string GetFileName(Context context, Android.Net.Uri uri)
+ {
+ string name = null;
+ string[] projection = { MediaStore.MediaColumns.DisplayName };
+ var metaCursor = context.ContentResolver.Query(uri, projection, null, null, null);
+ if(metaCursor != null)
+ {
+ try
+ {
+ if(metaCursor.MoveToFirst())
+ {
+ name = metaCursor.GetString(0);
+ }
+ }
+ finally
+ {
+ metaCursor.Close();
+ }
+ }
+ return name;
+ }
+ }
+}
diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs
index 8df346c4b..2cbfb60d9 100644
--- a/src/App/Abstractions/IDeviceActionService.cs
+++ b/src/App/Abstractions/IDeviceActionService.cs
@@ -13,6 +13,7 @@ namespace Bit.App.Abstractions
bool OpenFile(byte[] fileData, string id, string fileName);
bool CanOpenFile(string fileName);
Task ClearCacheAsync();
+ Task SelectFileAsync();
Task DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null);
}
diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/AddEditPage.xaml.cs
index 2b8075d37..e5d483967 100644
--- a/src/App/Pages/Vault/AddEditPage.xaml.cs
+++ b/src/App/Pages/Vault/AddEditPage.xaml.cs
@@ -99,11 +99,12 @@ namespace Bit.App.Pages
_vm.AddField();
}
- private void Attachments_Clicked(object sender, System.EventArgs e)
+ private async void Attachments_Clicked(object sender, System.EventArgs e)
{
if(DoOnce())
{
- // await Navigation.PushModalAsync();
+ var page = new AttachmentsPage(_vm.CipherId);
+ await Navigation.PushModalAsync(new NavigationPage(page));
}
}
diff --git a/src/App/Pages/Vault/AttachmentsPage.xaml b/src/App/Pages/Vault/AttachmentsPage.xaml
index a5f8f752e..d60a9ddbf 100644
--- a/src/App/Pages/Vault/AttachmentsPage.xaml
+++ b/src/App/Pages/Vault/AttachmentsPage.xaml
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AttachmentsPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
+ xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:AttachmentsPageViewModel"
@@ -21,6 +22,7 @@
+
@@ -28,22 +30,30 @@
-
+ IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
+
-
+
-
+
-
+
-
+ VerticalTextAlignment="Center"
+ HorizontalOptions="StartAndExpand" />
+
+
@@ -51,6 +61,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Vault/AttachmentsPage.xaml.cs b/src/App/Pages/Vault/AttachmentsPage.xaml.cs
index b237375ef..d570611e8 100644
--- a/src/App/Pages/Vault/AttachmentsPage.xaml.cs
+++ b/src/App/Pages/Vault/AttachmentsPage.xaml.cs
@@ -1,14 +1,19 @@
-using Xamarin.Forms;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using System;
+using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class AttachmentsPage : BaseContentPage
{
private AttachmentsPageViewModel _vm;
+ private readonly IBroadcasterService _broadcasterService;
public AttachmentsPage(string cipherId)
{
InitializeComponent();
+ _broadcasterService = ServiceContainer.Resolve("broadcasterService");
_vm = BindingContext as AttachmentsPageViewModel;
_vm.Page = this;
_vm.CipherId = cipherId;
@@ -18,20 +23,38 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
- await LoadOnAppearedAsync(_scrollView, true, () => _vm.LoadAsync());
+ _broadcasterService.Subscribe(nameof(AttachmentsPage), async (message) =>
+ {
+ if(message.Command == "selectFileResult")
+ {
+ var data = message.Data as Tuple;
+ _vm.FileData = data.Item1;
+ _vm.FileName = data.Item2;
+ }
+ });
+ await LoadOnAppearedAsync(_scrollView, true, () => _vm.InitAsync());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
+ _broadcasterService.Unsubscribe(nameof(AttachmentsPage));
}
- private async void Save_Clicked(object sender, System.EventArgs e)
+ private async void Save_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
await _vm.SubmitAsync();
}
}
+
+ private async void ChooseFile_Clicked(object sender, EventArgs e)
+ {
+ if(DoOnce())
+ {
+ await _vm.ChooseFileAsync();
+ }
+ }
}
}
diff --git a/src/App/Pages/Vault/AttachmentsPageViewModel.cs b/src/App/Pages/Vault/AttachmentsPageViewModel.cs
index 4f5af4672..f7f241786 100644
--- a/src/App/Pages/Vault/AttachmentsPageViewModel.cs
+++ b/src/App/Pages/Vault/AttachmentsPageViewModel.cs
@@ -8,6 +8,7 @@ using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -15,65 +16,103 @@ namespace Bit.App.Pages
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
- private readonly ICollectionService _collectionService;
+ private readonly ICryptoService _cryptoService;
+ private readonly IUserService _userService;
private readonly IPlatformUtilsService _platformUtilsService;
private CipherView _cipher;
private Cipher _cipherDomain;
- private bool _hasCollections;
+ private bool _hasAttachments;
+ private bool _hasUpdatedKey;
+ private bool _canAccessAttachments;
+ private string _fileName;
public AttachmentsPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve("deviceActionService");
_cipherService = ServiceContainer.Resolve("cipherService");
+ _cryptoService = ServiceContainer.Resolve("cryptoService");
_platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
- _collectionService = ServiceContainer.Resolve("collectionService");
- Collections = new ExtendedObservableCollection();
- PageTitle = AppResources.Collections;
+ _userService = ServiceContainer.Resolve("userService");
+ Attachments = new ExtendedObservableCollection();
+ DeleteAttachmentCommand = new Command(DeleteAsync);
+ PageTitle = AppResources.Attachments;
}
public string CipherId { get; set; }
- public ExtendedObservableCollection Collections { get; set; }
- public bool HasCollections
+ public CipherView Cipher
{
- get => _hasCollections;
- set => SetProperty(ref _hasCollections, value);
+ get => _cipher;
+ set => SetProperty(ref _cipher, value);
}
+ public ExtendedObservableCollection Attachments { get; set; }
+ public bool HasAttachments
+ {
+ get => _hasAttachments;
+ set => SetProperty(ref _hasAttachments, value);
+ }
+ public string FileName
+ {
+ get => _fileName;
+ set => SetProperty(ref _fileName, value);
+ }
+ public byte[] FileData { get; set; }
+ public Command DeleteAttachmentCommand { get; set; }
- public async Task LoadAsync()
+ public async Task InitAsync()
{
_cipherDomain = await _cipherService.GetAsync(CipherId);
- var collectionIds = _cipherDomain.CollectionIds;
- _cipher = await _cipherDomain.DecryptAsync();
- var allCollections = await _collectionService.GetAllDecryptedAsync();
- var collections = allCollections
- .Where(c => !c.ReadOnly && c.OrganizationId == _cipher.OrganizationId)
- .Select(c => new CollectionViewModel
+ Cipher = await _cipherDomain.DecryptAsync();
+ LoadAttachments();
+ _hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
+ var canAccessPremium = await _userService.CanAccessPremiumAsync();
+ _canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
+ if(!_canAccessAttachments)
+ {
+ await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
+ }
+ else if(!_hasUpdatedKey)
+ {
+ var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.UpdateKey,
+ AppResources.FeatureUnavailable, AppResources.LearnMore, AppResources.Cancel);
+ if(confirmed)
{
- Collection = c,
- Checked = collectionIds.Contains(c.Id)
- }).ToList();
- Collections.ResetWithRange(collections);
- HasCollections = Collections.Any();
+ _platformUtilsService.LaunchUri("https://help.bitwarden.com/article/update-encryption-key/");
+ }
+ }
}
public async Task SubmitAsync()
{
- if(!Collections.Any(c => c.Checked))
+ if(!_hasUpdatedKey)
{
- await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
- AppResources.Ok);
+ await _platformUtilsService.ShowDialogAsync(AppResources.UpdateKey,
+ AppResources.AnErrorHasOccurred);
+ return false;
+ }
+ if(FileData == null)
+ {
+ await _platformUtilsService.ShowDialogAsync(
+ string.Format(AppResources.ValidationFieldRequired, AppResources.File),
+ AppResources.AnErrorHasOccurred);
+ return false;
+ }
+ if(FileData.Length > 104857600) // 100 MB
+ {
+ await _platformUtilsService.ShowDialogAsync(AppResources.MaxFileSize,
+ AppResources.AnErrorHasOccurred);
return false;
}
-
- _cipherDomain.CollectionIds = new HashSet(
- Collections.Where(c => c.Checked).Select(c => c.Collection.Id));
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
- await _cipherService.SaveCollectionsWithServerAsync(_cipherDomain);
+ _cipherDomain = await _cipherService.SaveAttachmentRawWithServerAsync(
+ _cipherDomain, FileName, FileData);
+ Cipher = await _cipherDomain.DecryptAsync();
await _deviceActionService.HideLoadingAsync();
- _platformUtilsService.ShowToast("success", null, AppResources.ItemUpdated);
- await Page.Navigation.PopModalAsync();
+ _platformUtilsService.ShowToast("success", null, AppResources.AttachementAdded);
+ LoadAttachments();
+ FileData = null;
+ FileName = null;
return true;
}
catch(ApiException e)
@@ -83,5 +122,44 @@ namespace Bit.App.Pages
}
return false;
}
+
+ public async Task ChooseFileAsync()
+ {
+ await _deviceActionService.SelectFileAsync();
+ }
+
+ private async void DeleteAsync(AttachmentView attachment)
+ {
+ var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.DoYouReallyWantToDelete,
+ null, AppResources.Yes, AppResources.No);
+ if(!confirmed)
+ {
+ return;
+ }
+ try
+ {
+ await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
+ await _cipherService.DeleteAttachmentWithServerAsync(Cipher.Id, attachment.Id);
+ await _deviceActionService.HideLoadingAsync();
+ _platformUtilsService.ShowToast("success", null, AppResources.AttachmentDeleted);
+ var attachmentToRemove = Cipher.Attachments.FirstOrDefault(a => a.Id == attachment.Id);
+ if(attachmentToRemove != null)
+ {
+ Cipher.Attachments.Remove(attachmentToRemove);
+ LoadAttachments();
+ }
+ }
+ catch(ApiException e)
+ {
+ await _deviceActionService.HideLoadingAsync();
+ await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
+ }
+ }
+
+ private void LoadAttachments()
+ {
+ Attachments.ResetWithRange(Cipher.Attachments ?? new List());
+ HasAttachments = Cipher.HasAttachments;
+ }
}
}
diff --git a/src/App/Pages/Vault/SharePageViewModel.cs b/src/App/Pages/Vault/SharePageViewModel.cs
index 9d3456be7..9af5eaa4f 100644
--- a/src/App/Pages/Vault/SharePageViewModel.cs
+++ b/src/App/Pages/Vault/SharePageViewModel.cs
@@ -112,6 +112,11 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
}
+ catch(System.Exception e)
+ {
+ await _deviceActionService.HideLoadingAsync();
+ await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Message, AppResources.Ok);
+ }
return false;
}
diff --git a/src/App/Pages/Vault/ViewPage.xaml.cs b/src/App/Pages/Vault/ViewPage.xaml.cs
index 92930ecf6..d3dbb9cb9 100644
--- a/src/App/Pages/Vault/ViewPage.xaml.cs
+++ b/src/App/Pages/Vault/ViewPage.xaml.cs
@@ -105,11 +105,12 @@ namespace Bit.App.Pages
EditToolbarItem_Clicked(sender, e);
}
- private void Attachments_Clicked(object sender, System.EventArgs e)
+ private async void Attachments_Clicked(object sender, System.EventArgs e)
{
if(DoOnce())
{
- // await Navigation.PushModalAsync();
+ var page = new AttachmentsPage(_vm.CipherId);
+ await Navigation.PushModalAsync(new NavigationPage(page));
}
}
diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs
index 7029bce1b..e09205908 100644
--- a/src/Core/Constants.cs
+++ b/src/Core/Constants.cs
@@ -12,5 +12,7 @@
public static string LastFileCacheClearKey = "lastFileCacheClear";
public static string AccessibilityAutofillPasswordFieldKey = "accessibilityAutofillPasswordField";
public static string AccessibilityAutofillPersistNotificationKey = "accessibilityAutofillPersistNotification";
+ public const int SelectFileRequestCode = 42;
+ public const int SelectFilePermissionRequestCode = 43;
}
}
diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs
index f1f29c7e9..3bc42479b 100644
--- a/src/Core/Services/ApiService.cs
+++ b/src/Core/Services/ApiService.cs
@@ -249,7 +249,7 @@ namespace Bit.Core.Services
public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
{
return SendAsync