mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 01:48:25 +03:00
share page
This commit is contained in:
parent
9aef584494
commit
9668bd85c1
10 changed files with 384 additions and 17 deletions
|
@ -38,6 +38,9 @@
|
|||
<Compile Update="Pages\Generator\GeneratorPage.xaml.cs">
|
||||
<DependentUpon>GeneratorPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\SharePage.xaml.cs">
|
||||
<DependentUpon>SharePage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\CiphersPage.xaml.cs">
|
||||
<DependentUpon>CiphersPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
16
src/App/Pages/CollectionViewModel.cs
Normal file
16
src/App/Pages/CollectionViewModel.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class CollectionViewModel : ExtendedViewModel
|
||||
{
|
||||
private bool _checked;
|
||||
|
||||
public Core.Models.View.CollectionView Collection { get; set; }
|
||||
public bool Checked
|
||||
{
|
||||
get => _checked;
|
||||
set => SetProperty(ref _checked, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" />
|
||||
<ToolbarItem Text="{u:I18n Attachments}" Clicked="Attachments_Clicked" Order="Secondary" />
|
||||
<ToolbarItem Text="{u:I18n Share}" Clicked="Share_Clicked" Order="Secondary" />
|
||||
<ToolbarItem Text="{u:I18n Delete}" Clicked="Delete_Clicked" Order="Secondary" IsDestructive="True" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Resources>
|
||||
|
@ -534,7 +537,7 @@
|
|||
</StackLayout>
|
||||
<controls:RepeaterView ItemsSource="{Binding Collections}" IsVisible="{Binding HasCollections}">
|
||||
<controls:RepeaterView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:AddEditPageCollectionViewModel">
|
||||
<DataTemplate x:DataType="pages:CollectionViewModel">
|
||||
<StackLayout Spacing="0" Padding="0">
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
|
|
|
@ -76,5 +76,22 @@ namespace Bit.App.Pages
|
|||
// await Navigation.PushModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void Share_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if(DoOnce())
|
||||
{
|
||||
var page = new SharePage(_vm.CipherId);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
private async void Delete_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if(DoOnce())
|
||||
{
|
||||
await _vm.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ namespace Bit.App.Pages
|
|||
FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions);
|
||||
Uris = new ExtendedObservableCollection<LoginUriView>();
|
||||
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
|
||||
Collections = new ExtendedObservableCollection<AddEditPageCollectionViewModel>();
|
||||
Collections = new ExtendedObservableCollection<CollectionViewModel>();
|
||||
|
||||
TypeOptions = new List<KeyValuePair<string, CipherType>>
|
||||
{
|
||||
|
@ -149,7 +149,7 @@ namespace Bit.App.Pages
|
|||
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
|
||||
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
|
||||
public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; }
|
||||
public ExtendedObservableCollection<AddEditPageCollectionViewModel> Collections { get; set; }
|
||||
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
|
||||
public int TypeSelectedIndex
|
||||
{
|
||||
get => _typeSelectedIndex;
|
||||
|
@ -360,6 +360,13 @@ namespace Bit.App.Pages
|
|||
|
||||
if(!EditMode && Cipher.OrganizationId != null)
|
||||
{
|
||||
if(!Collections?.Any(c => c.Checked) ?? true)
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
|
||||
AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
Cipher.CollectionIds = Collections.Any() ?
|
||||
new HashSet<string>(Collections.Where(c => c.Checked).Select(c => c.Collection.Id)) : null;
|
||||
}
|
||||
|
@ -575,12 +582,12 @@ namespace Bit.App.Pages
|
|||
if(Cipher.OrganizationId != null)
|
||||
{
|
||||
var cols = _writeableCollections.Where(c => c.OrganizationId == Cipher.OrganizationId)
|
||||
.Select(c => new AddEditPageCollectionViewModel { Collection = c }).ToList();
|
||||
.Select(c => new CollectionViewModel { Collection = c }).ToList();
|
||||
Collections.ResetWithRange(cols);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collections.ResetWithRange(new List<AddEditPageCollectionViewModel>());
|
||||
Collections.ResetWithRange(new List<CollectionViewModel>());
|
||||
}
|
||||
HasCollections = Collections.Any();
|
||||
}
|
||||
|
@ -615,18 +622,6 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
public class AddEditPageCollectionViewModel : ExtendedViewModel
|
||||
{
|
||||
private bool _checked;
|
||||
|
||||
public Core.Models.View.CollectionView Collection { get; set; }
|
||||
public bool Checked
|
||||
{
|
||||
get => _checked;
|
||||
set => SetProperty(ref _checked, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class AddEditPageFieldViewModel : ExtendedViewModel
|
||||
{
|
||||
private FieldView _field;
|
||||
|
|
81
src/App/Pages/Vault/SharePage.xaml
Normal file
81
src/App/Pages/Vault/SharePage.xaml
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.SharePage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:DataType="pages:SharePageViewModel"
|
||||
x:Name="_page"
|
||||
Title="{Binding PageTitle}">
|
||||
<ContentPage.BindingContext>
|
||||
<pages:SharePageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:IsNotNullConverter x:Key="notNull" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ScrollView x:Name="_scrollView">
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row"
|
||||
IsVisible="{Binding HasOrganizations, Converter={StaticResource inverseBool}}">
|
||||
<Label Text="{u:I18n NoOrganizationsToList}" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row, box-row-input">
|
||||
<Label
|
||||
Text="{u:I18n Organization}"
|
||||
StyleClass="box-label" />
|
||||
<Picker
|
||||
x:Name="_organizationPicker"
|
||||
ItemsSource="{Binding OrganizationOptions, Mode=OneTime}"
|
||||
SelectedIndex="{Binding OrganizationSelectedIndex}"
|
||||
StyleClass="box-value" />
|
||||
</StackLayout>
|
||||
<Label
|
||||
Text="{u:I18n ShareDesc}"
|
||||
StyleClass="box-footer-label" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box"
|
||||
IsVisible="{Binding OrganizationId, Converter={StaticResource notNull}}">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="{u:I18n Collections}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row"
|
||||
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
|
||||
<Label Text="{u:I18n NoCollectionsToList}" />
|
||||
</StackLayout>
|
||||
<controls:RepeaterView ItemsSource="{Binding Collections}" IsVisible="{Binding HasCollections}">
|
||||
<controls:RepeaterView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:CollectionViewModel">
|
||||
<StackLayout Spacing="0" Padding="0">
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="{Binding Collection.Name}"
|
||||
StyleClass="box-label, box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding Checked}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</controls:RepeaterView.ItemTemplate>
|
||||
</controls:RepeaterView>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</pages:BaseContentPage>
|
38
src/App/Pages/Vault/SharePage.xaml.cs
Normal file
38
src/App/Pages/Vault/SharePage.xaml.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class SharePage : BaseContentPage
|
||||
{
|
||||
private SharePageViewModel _vm;
|
||||
|
||||
public SharePage(string cipherId = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as SharePageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.CipherId = cipherId;
|
||||
SetActivityIndicator();
|
||||
_organizationPicker.ItemDisplayBinding = new Binding("Key");
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
await LoadOnAppearedAsync(_scrollView, true, () => _vm.LoadAsync());
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
}
|
||||
|
||||
private async void Save_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if(DoOnce())
|
||||
{
|
||||
await _vm.SubmitAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
src/App/Pages/Vault/SharePageViewModel.cs
Normal file
142
src/App/Pages/Vault/SharePageViewModel.cs
Normal file
|
@ -0,0 +1,142 @@
|
|||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class SharePageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private CipherView _cipher;
|
||||
private int _organizationSelectedIndex;
|
||||
private bool _hasCollections;
|
||||
private bool _hasOrganizations;
|
||||
private List<Core.Models.View.CollectionView> _writeableCollections;
|
||||
|
||||
public SharePageViewModel()
|
||||
{
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||
Collections = new ExtendedObservableCollection<CollectionViewModel>();
|
||||
OrganizationOptions = new List<KeyValuePair<string, string>>();
|
||||
PageTitle = AppResources.Share;
|
||||
}
|
||||
|
||||
public string CipherId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public List<KeyValuePair<string, string>> OrganizationOptions { get; set; }
|
||||
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
|
||||
public int OrganizationSelectedIndex
|
||||
{
|
||||
get => _organizationSelectedIndex;
|
||||
set
|
||||
{
|
||||
if(SetProperty(ref _organizationSelectedIndex, value))
|
||||
{
|
||||
OrganizationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool HasCollections
|
||||
{
|
||||
get => _hasCollections;
|
||||
set => SetProperty(ref _hasCollections, value);
|
||||
}
|
||||
public bool HasOrganizations
|
||||
{
|
||||
get => _hasOrganizations;
|
||||
set => SetProperty(ref _hasOrganizations, value);
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
var allCollections = await _collectionService.GetAllDecryptedAsync();
|
||||
_writeableCollections = allCollections.Where(c => !c.ReadOnly).ToList();
|
||||
|
||||
var orgs = await _userService.GetAllOrganizationAsync();
|
||||
OrganizationOptions = orgs.OrderBy(o => o.Name)
|
||||
.Where(o => o.Enabled && o.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Select(o => new KeyValuePair<string, string>(o.Name, o.Id)).ToList();
|
||||
HasOrganizations = OrganizationOptions.Any();
|
||||
|
||||
var cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||
_cipher = await cipherDomain.DecryptAsync();
|
||||
if(OrganizationId == null && OrganizationOptions.Any())
|
||||
{
|
||||
OrganizationId = OrganizationOptions.First().Value;
|
||||
}
|
||||
OrganizationSelectedIndex = string.IsNullOrWhiteSpace(OrganizationId) ? 0 :
|
||||
OrganizationOptions.FindIndex(k => k.Value == OrganizationId);
|
||||
FilterCollections();
|
||||
}
|
||||
|
||||
public async Task<bool> SubmitAsync()
|
||||
{
|
||||
if(!Collections?.Any(c => c.Checked) ?? true)
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
|
||||
AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
var cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||
var cipherView = await cipherDomain.DecryptAsync();
|
||||
|
||||
var checkedCollectionIds = new HashSet<string>(
|
||||
Collections.Where(c => c.Checked).Select(c => c.Collection.Id));
|
||||
try
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
|
||||
await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
_platformUtilsService.ShowToast("success", null, AppResources.ItemShared);
|
||||
await Page.Navigation.PopModalAsync();
|
||||
return true;
|
||||
}
|
||||
catch(ApiException e)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OrganizationChanged()
|
||||
{
|
||||
if(OrganizationSelectedIndex > -1)
|
||||
{
|
||||
OrganizationId = OrganizationOptions[OrganizationSelectedIndex].Value;
|
||||
FilterCollections();
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterCollections()
|
||||
{
|
||||
if(OrganizationId == null || !_writeableCollections.Any())
|
||||
{
|
||||
Collections.ResetWithRange(new List<CollectionViewModel>());
|
||||
}
|
||||
else
|
||||
{
|
||||
var cols = _writeableCollections.Where(c => c.OrganizationId == OrganizationId)
|
||||
.Select(c => new CollectionViewModel { Collection = c }).ToList();
|
||||
Collections.ResetWithRange(cols);
|
||||
}
|
||||
HasCollections = Collections.Any();
|
||||
}
|
||||
}
|
||||
}
|
54
src/App/Resources/AppResources.Designer.cs
generated
54
src/App/Resources/AppResources.Designer.cs
generated
|
@ -1905,6 +1905,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Item has been shared..
|
||||
/// </summary>
|
||||
public static string ItemShared {
|
||||
get {
|
||||
return ResourceManager.GetString("ItemShared", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Item updated..
|
||||
/// </summary>
|
||||
|
@ -2517,6 +2526,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No organizations to list..
|
||||
/// </summary>
|
||||
public static string NoOrgsToList {
|
||||
get {
|
||||
return ResourceManager.GetString("NoOrgsToList", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No passwords to list..
|
||||
/// </summary>
|
||||
|
@ -2985,6 +3003,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You must select at least one collection..
|
||||
/// </summary>
|
||||
public static string SelectOneCollection {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectOneCollection", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to What type of item do you want to add?.
|
||||
/// </summary>
|
||||
|
@ -3075,6 +3102,33 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Share.
|
||||
/// </summary>
|
||||
public static string Share {
|
||||
get {
|
||||
return ResourceManager.GetString("Share", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared..
|
||||
/// </summary>
|
||||
public static string ShareDesc {
|
||||
get {
|
||||
return ResourceManager.GetString("ShareDesc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Share Item.
|
||||
/// </summary>
|
||||
public static string ShareItem {
|
||||
get {
|
||||
return ResourceManager.GetString("ShareItem", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Share Your Vault.
|
||||
/// </summary>
|
||||
|
|
|
@ -1429,4 +1429,22 @@
|
|||
<data name="NoCollectionsToList" xml:space="preserve">
|
||||
<value>There are no collections to list.</value>
|
||||
</data>
|
||||
<data name="ItemShared" xml:space="preserve">
|
||||
<value>Item has been shared.</value>
|
||||
</data>
|
||||
<data name="SelectOneCollection" xml:space="preserve">
|
||||
<value>You must select at least one collection.</value>
|
||||
</data>
|
||||
<data name="Share" xml:space="preserve">
|
||||
<value>Share</value>
|
||||
</data>
|
||||
<data name="ShareItem" xml:space="preserve">
|
||||
<value>Share Item</value>
|
||||
</data>
|
||||
<data name="NoOrgsToList" xml:space="preserve">
|
||||
<value>No organizations to list.</value>
|
||||
</data>
|
||||
<data name="ShareDesc" xml:space="preserve">
|
||||
<value>Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared.</value>
|
||||
</data>
|
||||
</root>
|
Loading…
Reference in a new issue