1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-11-22 11:35:21 +01:00

Support for Disable Send policy (#1271)

* add support for disable send policy

* cleanup

* show/hide options support for send search results

* additional failsafes and copy function consolidation

* added missing disabled send icon to android renderer

* async fix and string updates
This commit is contained in:
Matt Portune 2021-02-18 16:58:20 -05:00 committed by GitHub
parent 20d5c6a63a
commit 3799eb4603
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 235 additions and 42 deletions

View File

@ -102,6 +102,7 @@ namespace Bit.Droid.Renderers
Icon = view.FindViewById<TextView>(Resource.Id.SendCellIcon); Icon = view.FindViewById<TextView>(Resource.Id.SendCellIcon);
Name = view.FindViewById<TextView>(Resource.Id.SendCellName); Name = view.FindViewById<TextView>(Resource.Id.SendCellName);
SubTitle = view.FindViewById<TextView>(Resource.Id.SendCellSubTitle); SubTitle = view.FindViewById<TextView>(Resource.Id.SendCellSubTitle);
DisabledIcon = view.FindViewById<TextView>(Resource.Id.SendCellDisabledIcon);
HasPasswordIcon = view.FindViewById<TextView>(Resource.Id.SendCellHasPasswordIcon); HasPasswordIcon = view.FindViewById<TextView>(Resource.Id.SendCellHasPasswordIcon);
MaxAccessCountReachedIcon = view.FindViewById<TextView>(Resource.Id.SendCellMaxAccessCountReachedIcon); MaxAccessCountReachedIcon = view.FindViewById<TextView>(Resource.Id.SendCellMaxAccessCountReachedIcon);
ExpiredIcon = view.FindViewById<TextView>(Resource.Id.SendCellExpiredIcon); ExpiredIcon = view.FindViewById<TextView>(Resource.Id.SendCellExpiredIcon);
@ -110,6 +111,7 @@ namespace Bit.Droid.Renderers
MoreButton.Click += MoreButton_Click; MoreButton.Click += MoreButton_Click;
Icon.Typeface = _faTypeface; Icon.Typeface = _faTypeface;
DisabledIcon.Typeface = _faTypeface;
HasPasswordIcon.Typeface = _faTypeface; HasPasswordIcon.Typeface = _faTypeface;
MaxAccessCountReachedIcon.Typeface = _faTypeface; MaxAccessCountReachedIcon.Typeface = _faTypeface;
ExpiredIcon.Typeface = _faTypeface; ExpiredIcon.Typeface = _faTypeface;
@ -120,12 +122,18 @@ namespace Bit.Droid.Renderers
Icon.SetTextSize(ComplexUnitType.Pt, 10); Icon.SetTextSize(ComplexUnitType.Pt, 10);
Name.SetTextSize(ComplexUnitType.Sp, (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label))); Name.SetTextSize(ComplexUnitType.Sp, (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label)));
SubTitle.SetTextSize(ComplexUnitType.Sp, small); SubTitle.SetTextSize(ComplexUnitType.Sp, small);
DisabledIcon.SetTextSize(ComplexUnitType.Sp, small);
HasPasswordIcon.SetTextSize(ComplexUnitType.Sp, small); HasPasswordIcon.SetTextSize(ComplexUnitType.Sp, small);
MaxAccessCountReachedIcon.SetTextSize(ComplexUnitType.Sp, small); MaxAccessCountReachedIcon.SetTextSize(ComplexUnitType.Sp, small);
ExpiredIcon.SetTextSize(ComplexUnitType.Sp, small); ExpiredIcon.SetTextSize(ComplexUnitType.Sp, small);
PendingDeleteIcon.SetTextSize(ComplexUnitType.Sp, small); PendingDeleteIcon.SetTextSize(ComplexUnitType.Sp, small);
MoreButton.SetTextSize(ComplexUnitType.Sp, 25); MoreButton.SetTextSize(ComplexUnitType.Sp, 25);
if (!SendViewCell.ShowOptions)
{
MoreButton.Visibility = ViewStates.Gone;
}
AddView(view); AddView(view);
} }
@ -135,6 +143,7 @@ namespace Bit.Droid.Renderers
public TextView Icon { get; set; } public TextView Icon { get; set; }
public TextView Name { get; set; } public TextView Name { get; set; }
public TextView SubTitle { get; set; } public TextView SubTitle { get; set; }
public TextView DisabledIcon { get; set; }
public TextView HasPasswordIcon { get; set; } public TextView HasPasswordIcon { get; set; }
public TextView MaxAccessCountReachedIcon { get; set; } public TextView MaxAccessCountReachedIcon { get; set; }
public TextView ExpiredIcon { get; set; } public TextView ExpiredIcon { get; set; }
@ -148,6 +157,7 @@ namespace Bit.Droid.Renderers
var send = sendCell.Send; var send = sendCell.Send;
Name.Text = send.Name; Name.Text = send.Name;
SubTitle.Text = send.DisplayDate; SubTitle.Text = send.DisplayDate;
DisabledIcon.Visibility = send.Disabled ? ViewStates.Visible : ViewStates.Gone;
HasPasswordIcon.Visibility = send.HasPassword ? ViewStates.Visible : ViewStates.Gone; HasPasswordIcon.Visibility = send.HasPassword ? ViewStates.Visible : ViewStates.Gone;
MaxAccessCountReachedIcon.Visibility = send.MaxAccessCountReached ? ViewStates.Visible : ViewStates.Gone; MaxAccessCountReachedIcon.Visibility = send.MaxAccessCountReached ? ViewStates.Visible : ViewStates.Gone;
ExpiredIcon.Visibility = send.Expired ? ViewStates.Visible : ViewStates.Gone; ExpiredIcon.Visibility = send.Expired ? ViewStates.Visible : ViewStates.Gone;
@ -172,6 +182,7 @@ namespace Bit.Droid.Renderers
Name.SetTextColor(textColor); Name.SetTextColor(textColor);
SubTitle.SetTextColor(mutedColor); SubTitle.SetTextColor(mutedColor);
Icon.SetTextColor(mutedColor); Icon.SetTextColor(mutedColor);
DisabledIcon.SetTextColor(mutedColor);
HasPasswordIcon.SetTextColor(mutedColor); HasPasswordIcon.SetTextColor(mutedColor);
MaxAccessCountReachedIcon.SetTextColor(mutedColor); MaxAccessCountReachedIcon.SetTextColor(mutedColor);
ExpiredIcon.SetTextColor(mutedColor); ExpiredIcon.SetTextColor(mutedColor);

View File

@ -43,6 +43,14 @@
android:singleLine="true" android:singleLine="true"
android:ellipsize="end" android:ellipsize="end"
android:text="Name" /> android:text="Name" />
<TextView
android:id="@+id/SendCellDisabledIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf071;" />
<TextView <TextView
android:id="@+id/SendCellHasPasswordIcon" android:id="@+id/SendCellHasPasswordIcon"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -124,6 +124,7 @@
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Text="&#xe5d3;" Text="&#xe5d3;"
IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled" StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked" Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"

View File

@ -15,6 +15,9 @@ namespace Bit.App.Controls
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create( public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell)); nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
nameof(ShowOptions), typeof(bool), typeof(SendViewCell));
private readonly IEnvironmentService _environmentService; private readonly IEnvironmentService _environmentService;
@ -47,6 +50,12 @@ namespace Bit.App.Controls
set => SetValue(ButtonCommandProperty, value); set => SetValue(ButtonCommandProperty, value);
} }
public bool ShowOptions
{
get => GetValue(ShowOptionsProperty) is bool && (bool)GetValue(ShowOptionsProperty);
set => SetValue(ShowOptionsProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null) protected override void OnPropertyChanged(string propertyName = null)
{ {
base.OnPropertyChanged(propertyName); base.OnPropertyChanged(propertyName);
@ -58,6 +67,10 @@ namespace Bit.App.Controls
{ {
_viewModel.Send = Send; _viewModel.Send = Send;
} }
else if (propertyName == ShowOptionsProperty.PropertyName)
{
_viewModel.ShowOptions = ShowOptions;
}
} }
protected override void OnBindingContextChanged() protected override void OnBindingContextChanged()

View File

@ -6,11 +6,18 @@ namespace Bit.App.Controls
public class SendViewCellViewModel : ExtendedViewModel public class SendViewCellViewModel : ExtendedViewModel
{ {
private SendView _send; private SendView _send;
private bool _showOptions;
public SendView Send public SendView Send
{ {
get => _send; get => _send;
set => SetProperty(ref _send, value); set => SetProperty(ref _send, value);
} }
public bool ShowOptions
{
get => _showOptions;
set => SetProperty(ref _showOptions, value);
}
} }
} }

View File

@ -14,7 +14,8 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" /> <ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary"
x:Key="saveItem" x:Name="_saveItem"/>
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ContentPage.Resources> <ContentPage.Resources>
@ -76,6 +77,18 @@
<ScrollView x:Key="scrollView" x:Name="_scrollView"> <ScrollView x:Key="scrollView" x:Name="_scrollView">
<StackLayout Spacing="20"> <StackLayout Spacing="20">
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<Frame
IsVisible="{Binding SendEnabled, Converter={StaticResource inverseBool}}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label
Text="{u:I18n Name}" Text="{u:I18n Name}"
@ -83,6 +96,7 @@
<Entry <Entry
x:Name="_nameEntry" x:Name="_nameEntry"
Text="{Binding Send.Name}" Text="{Binding Send.Name}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" /> StyleClass="box-value" />
<Label <Label
Text="{u:I18n NameInfo}" Text="{u:I18n NameInfo}"
@ -186,6 +200,7 @@
HorizontalTextAlignment="Center" /> HorizontalTextAlignment="Center" />
<Button <Button
Text="{u:I18n ChooseFile}" Text="{u:I18n ChooseFile}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-button-row" StyleClass="box-button-row"
Clicked="ChooseFile_Clicked" /> Clicked="ChooseFile_Clicked" />
<Label <Label
@ -210,6 +225,7 @@
x:Name="_textEditor" x:Name="_textEditor"
AutoSize="TextChanges" AutoSize="TextChanges"
Text="{Binding Send.Text.Text}" Text="{Binding Send.Text.Text}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
Margin="{Binding EditorMargins}" /> Margin="{Binding EditorMargins}" />
<BoxView <BoxView
@ -229,6 +245,7 @@
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding Send.Text.Hidden}" IsToggled="{Binding Send.Text.Hidden}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End" HorizontalOptions="End"
Margin="10,0,0,0" /> Margin="10,0,0,0" />
</StackLayout> </StackLayout>
@ -241,6 +258,7 @@
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding ShareOnSave}" IsToggled="{Binding ShareOnSave}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End" HorizontalOptions="End"
Margin="10,0,0,0" /> Margin="10,0,0,0" />
</StackLayout> </StackLayout>
@ -281,6 +299,7 @@
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}" IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding DeletionTypeOptions, Mode=OneTime}" ItemsSource="{Binding DeletionTypeOptions, Mode=OneTime}"
SelectedIndex="{Binding DeletionDateTypeSelectedIndex}" SelectedIndex="{Binding DeletionDateTypeSelectedIndex}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionTime}" /> AutomationProperties.Name="{u:I18n DeletionTime}" />
@ -294,12 +313,14 @@
<controls:ExtendedDatePicker <controls:ExtendedDatePicker
NullableDate="{Binding DeletionDate, Mode=TwoWay}" NullableDate="{Binding DeletionDate, Mode=TwoWay}"
Format="d" Format="d"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionDate}" AutomationProperties.Name="{u:I18n DeletionDate}"
Grid.Column="0" /> Grid.Column="0" />
<controls:ExtendedTimePicker <controls:ExtendedTimePicker
NullableTime="{Binding DeletionTime, Mode=TwoWay}" NullableTime="{Binding DeletionTime, Mode=TwoWay}"
Format="t" Format="t"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionTime}" AutomationProperties.Name="{u:I18n DeletionTime}"
Grid.Column="1" /> Grid.Column="1" />
@ -318,6 +339,7 @@
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}" IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding ExpirationTypeOptions, Mode=OneTime}" ItemsSource="{Binding ExpirationTypeOptions, Mode=OneTime}"
SelectedIndex="{Binding ExpirationDateTypeSelectedIndex}" SelectedIndex="{Binding ExpirationDateTypeSelectedIndex}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationTime}" /> AutomationProperties.Name="{u:I18n ExpirationTime}" />
@ -332,6 +354,7 @@
NullableDate="{Binding ExpirationDate, Mode=TwoWay}" NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
PlaceHolder="mm/dd/yyyy" PlaceHolder="mm/dd/yyyy"
Format="d" Format="d"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationDate}" AutomationProperties.Name="{u:I18n ExpirationDate}"
Grid.Column="0" /> Grid.Column="0" />
@ -339,6 +362,7 @@
NullableTime="{Binding ExpirationTime, Mode=TwoWay}" NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
PlaceHolder="--:-- --" PlaceHolder="--:-- --"
Format="t" Format="t"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationTime}" AutomationProperties.Name="{u:I18n ExpirationTime}"
Grid.Column="1" /> Grid.Column="1" />
@ -356,6 +380,7 @@
WidthRequest="110" WidthRequest="110"
HeightRequest="{Binding SegmentedButtonHeight}" HeightRequest="{Binding SegmentedButtonHeight}"
FontSize="{Binding SegmentedButtonFontSize}" FontSize="{Binding SegmentedButtonFontSize}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-row-button" StyleClass="box-row-button"
Clicked="ClearExpirationDate_Clicked" /> Clicked="ClearExpirationDate_Clicked" />
</StackLayout> </StackLayout>
@ -371,6 +396,7 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Entry <Entry
Text="{Binding MaxAccessCount}" Text="{Binding MaxAccessCount}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
Keyboard="Numeric" Keyboard="Numeric"
MaxLength="9" MaxLength="9"
@ -380,6 +406,7 @@
x:Name="_maxAccessCountStepper" x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}" Value="{Binding MaxAccessCount}"
Maximum="999999999" Maximum="999999999"
IsEnabled="{Binding SendEnabled}"
Margin="10,0,0,0" /> Margin="10,0,0,0" />
</StackLayout> </StackLayout>
<Label <Label
@ -413,11 +440,13 @@
<Entry <Entry
Text="{Binding NewPassword}" Text="{Binding NewPassword}"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}" IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
IsSpellCheckEnabled="False" IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" IsTextPredictionEnabled="False"
HorizontalOptions="FillAndExpand" /> HorizontalOptions="FillAndExpand" />
<controls:FaButton <controls:FaButton
IsEnabled="{Binding SendEnabled}"
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}" Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}" Command="{Binding TogglePasswordCommand}"
@ -439,6 +468,7 @@
<Editor <Editor
AutoSize="TextChanges" AutoSize="TextChanges"
Text="{Binding Send.Notes}" Text="{Binding Send.Notes}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" StyleClass="box-value"
Margin="{Binding EditorMargins}" /> Margin="{Binding EditorMargins}" />
<BoxView <BoxView
@ -459,6 +489,7 @@
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Switch <Switch
IsToggled="{Binding Send.Disabled}" IsToggled="{Binding Send.Disabled}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End" HorizontalOptions="End"
Margin="10,0,0,0" /> Margin="10,0,0,0" />
</StackLayout> </StackLayout>

View File

@ -27,7 +27,6 @@ namespace Bit.App.Pages
_vm.Page = this; _vm.Page = this;
_vm.SendId = sendId; _vm.SendId = sendId;
_vm.Type = type; _vm.Type = type;
_vm.Init();
SetActivityIndicator(); SetActivityIndicator();
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@ -42,7 +41,7 @@ namespace Bit.App.Pages
_vm.SegmentedButtonFontSize = 13; _vm.SegmentedButtonFontSize = 13;
_vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0); _vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0);
_vm.EditorMargins = new Thickness(0, 5, 0, 0); _vm.EditorMargins = new Thickness(0, 5, 0, 0);
_btnOptions.WidthRequest = 62; _btnOptions.WidthRequest = 70;
_btnOptionsDown.WidthRequest = 30; _btnOptionsDown.WidthRequest = 30;
_btnOptionsUp.WidthRequest = 30; _btnOptionsUp.WidthRequest = 30;
} }
@ -75,6 +74,7 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
await _vm.InitAsync();
_broadcasterService.Subscribe(nameof(SendAddEditPage), message => _broadcasterService.Subscribe(nameof(SendAddEditPage), message =>
{ {
if (message.Command == "selectFileResult") if (message.Command == "selectFileResult")
@ -99,6 +99,7 @@ namespace Bit.App.Pages
{ {
RequestFocus(_nameEntry); RequestFocus(_nameEntry);
} }
AdjustToolbar();
}); });
} }
@ -111,9 +112,9 @@ namespace Bit.App.Pages
} }
} }
private void TextType_Clicked(object sender, EventArgs eventArgs) private async void TextType_Clicked(object sender, EventArgs eventArgs)
{ {
_vm.TypeChanged(SendType.Text); await _vm.TypeChangedAsync(SendType.Text);
_nameEntry.ReturnType = ReturnType.Next; _nameEntry.ReturnType = ReturnType.Next;
_nameEntry.ReturnCommand = new Command(() => _textEditor.Focus()); _nameEntry.ReturnCommand = new Command(() => _textEditor.Focus());
if (string.IsNullOrWhiteSpace(_vm.Send.Name)) if (string.IsNullOrWhiteSpace(_vm.Send.Name))
@ -122,9 +123,9 @@ namespace Bit.App.Pages
} }
} }
private void FileType_Clicked(object sender, EventArgs eventArgs) private async void FileType_Clicked(object sender, EventArgs eventArgs)
{ {
_vm.TypeChanged(SendType.File); await _vm.TypeChangedAsync(SendType.File);
_nameEntry.ReturnType = ReturnType.Done; _nameEntry.ReturnType = ReturnType.Done;
_nameEntry.ReturnCommand = null; _nameEntry.ReturnCommand = null;
if (string.IsNullOrWhiteSpace(_vm.Send.Name)) if (string.IsNullOrWhiteSpace(_vm.Send.Name))
@ -219,12 +220,12 @@ namespace Bit.App.Pages
return; return;
} }
var options = new List<string>(); var options = new List<string>();
if (_vm.Send.HasPassword) if (_vm.SendEnabled && _vm.EditMode)
{
options.Add(AppResources.RemovePassword);
}
if (_vm.EditMode)
{ {
if (_vm.Send.HasPassword)
{
options.Add(AppResources.RemovePassword);
}
options.Add(AppResources.CopyLink); options.Add(AppResources.CopyLink);
options.Add(AppResources.ShareLink); options.Add(AppResources.ShareLink);
} }
@ -259,5 +260,16 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
} }
} }
private void AdjustToolbar()
{
_saveItem.IsEnabled = _vm.SendEnabled;
if (!_vm.SendEnabled && _vm.EditMode && Device.RuntimePlatform == Device.Android)
{
ToolbarItems.Remove(_removePassword);
ToolbarItems.Remove(_copyLink);
ToolbarItems.Remove(_shareLink);
}
}
} }
} }

View File

@ -18,7 +18,9 @@ namespace Bit.App.Pages
{ {
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IUserService _userService;
private readonly ISendService _sendService; private readonly ISendService _sendService;
private bool _sendEnabled;
private bool _canAccessPremium; private bool _canAccessPremium;
private SendView _send; private SendView _send;
private string _fileName; private string _fileName;
@ -42,6 +44,7 @@ namespace Bit.App.Pages
{ {
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_sendService = ServiceContainer.Resolve<ISendService>("sendService"); _sendService = ServiceContainer.Resolve<ISendService>("sendService");
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@ -87,6 +90,11 @@ namespace Bit.App.Pages
public List<KeyValuePair<string, SendType>> TypeOptions { get; } public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; } public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; } public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
public bool SendEnabled
{
get => _sendEnabled;
set => SetProperty(ref _sendEnabled, value);
}
public int DeletionDateTypeSelectedIndex public int DeletionDateTypeSelectedIndex
{ {
get => _deletionDateTypeSelectedIndex; get => _deletionDateTypeSelectedIndex;
@ -189,16 +197,15 @@ namespace Bit.App.Pages
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7; public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
public string ShowPasswordIcon => ShowPassword ? "" : ""; public string ShowPasswordIcon => ShowPassword ? "" : "";
public void Init() public async Task InitAsync()
{ {
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend; PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
} }
public async Task<bool> LoadAsync() public async Task<bool> LoadAsync()
{ {
var userService = ServiceContainer.Resolve<IUserService>("userService");
_canAccessPremium = await userService.CanAccessPremiumAsync();
// TODO Policy Check
if (Send == null) if (Send == null)
{ {
_isOverridingPickers = true; _isOverridingPickers = true;
@ -284,7 +291,7 @@ namespace Bit.App.Pages
public async Task<bool> SubmitAsync() public async Task<bool> SubmitAsync()
{ {
if (Send == null) if (Send == null || !SendEnabled)
{ {
return false; return false;
} }
@ -349,7 +356,7 @@ namespace Bit.App.Pages
if (savedSend != null) if (savedSend != null)
{ {
var savedSendView = await savedSend.DecryptAsync(); var savedSendView = await savedSend.DecryptAsync();
await AppHelpers.ShareSendUrl(savedSendView); await AppHelpers.ShareSendUrlAsync(savedSendView);
} }
} }
@ -374,14 +381,12 @@ namespace Bit.App.Pages
public async Task CopyLinkAsync() public async Task CopyLinkAsync()
{ {
await _platformUtilsService.CopyToClipboardAsync(AppHelpers.GetSendUrl(Send)); await AppHelpers.CopySendUrlAsync(Send);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.ShareLink));
} }
public async Task ShareLinkAsync() public async Task ShareLinkAsync()
{ {
await AppHelpers.ShareSendUrl(Send); await AppHelpers.ShareSendUrlAsync(Send);
} }
public async Task<bool> DeleteAsync() public async Task<bool> DeleteAsync()
@ -389,7 +394,7 @@ namespace Bit.App.Pages
return await AppHelpers.DeleteSendAsync(SendId); return await AppHelpers.DeleteSendAsync(SendId);
} }
public async void TypeChanged(SendType type) public async Task TypeChangedAsync(SendType type)
{ {
if (Send != null) if (Send != null)
{ {

View File

@ -37,7 +37,8 @@
x:DataType="pages:SendGroupingsPageListItem"> x:DataType="pages:SendGroupingsPageListItem">
<controls:SendViewCell <controls:SendViewCell
Send="{Binding Send}" Send="{Binding Send}"
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}" /> ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
ShowOptions="{Binding ShowOptions}" />
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="sendGroupTemplate" <DataTemplate x:Key="sendGroupTemplate"
@ -72,6 +73,20 @@
GroupTemplate="{StaticResource sendGroupTemplate}" /> GroupTemplate="{StaticResource sendGroupTemplate}" />
<StackLayout x:Key="mainLayout" x:Name="_mainLayout"> <StackLayout x:Key="mainLayout" x:Name="_mainLayout">
<StackLayout StyleClass="box">
<Frame
IsVisible="{Binding SendEnabled, Converter={StaticResource inverseBool}}"
Padding="10"
Margin="0, 12, 0, 6"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</StackLayout>
<StackLayout <StackLayout
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Padding="20, 0" Padding="20, 0"
@ -143,6 +158,7 @@
<Button <Button
x:Name="_fab" x:Name="_fab"
Image="plus.png" Image="plus.png"
IsVisible="{Binding SendEnabled}"
Clicked="AddButton_Clicked" Clicked="AddButton_Clicked"
Style="{StaticResource btn-fab}" Style="{StaticResource btn-fab}"
AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutFlags="PositionProportional"

View File

@ -55,9 +55,10 @@ namespace Bit.App.Pages
public ExtendedListView ListView { get; set; } public ExtendedListView ListView { get; set; }
protected async override void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
await _vm.InitAsync();
if (_syncService.SyncInProgress) if (_syncService.SyncInProgress)
{ {
IsBusy = true; IsBusy = true;
@ -107,6 +108,7 @@ namespace Bit.App.Pages
} }
await ShowPreviousPageAsync(); await ShowPreviousPageAsync();
AdjustToolbar();
}, _mainContent); }, _mainContent);
} }
@ -184,5 +186,10 @@ namespace Bit.App.Pages
} }
_previousPage = null; _previousPage = null;
} }
private void AdjustToolbar()
{
_addItem.IsEnabled = _vm.SendEnabled;
}
} }
} }

View File

@ -12,6 +12,7 @@ namespace Bit.App.Pages
public SendView Send { get; set; } public SendView Send { get; set; }
public SendType? Type { get; set; } public SendType? Type { get; set; }
public string ItemCount { get; set; } public string ItemCount { get; set; }
public bool ShowOptions { get; set; }
public string Name public string Name
{ {

View File

@ -18,6 +18,7 @@ namespace Bit.App.Pages
{ {
public class SendGroupingsPageViewModel : BaseViewModel public class SendGroupingsPageViewModel : BaseViewModel
{ {
private bool _sendEnabled;
private bool _refreshing; private bool _refreshing;
private bool _doingLoad; private bool _doingLoad;
private bool _loading; private bool _loading;
@ -65,6 +66,11 @@ namespace Bit.App.Pages
public bool HasSends { get; set; } public bool HasSends { get; set; }
public List<SendView> Sends { get; set; } public List<SendView> Sends { get; set; }
public bool SendEnabled
{
get => _sendEnabled;
set => SetProperty(ref _sendEnabled, value);
}
public bool Refreshing public bool Refreshing
{ {
get => _refreshing; get => _refreshing;
@ -110,6 +116,11 @@ namespace Bit.App.Pages
public Command<SendView> SendOptionsCommand { get; set; } public Command<SendView> SendOptionsCommand { get; set; }
public bool LoadedOnce { get; set; } public bool LoadedOnce { get; set; }
public async Task InitAsync()
{
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
}
public async Task LoadAsync() public async Task LoadAsync()
{ {
if (_doingLoad) if (_doingLoad)
@ -167,7 +178,11 @@ namespace Bit.App.Pages
if (Sends?.Any() ?? false) if (Sends?.Any() ?? false)
{ {
var sendsListItems = Sends.Select(s => new SendGroupingsPageListItem { Send = s }).ToList(); var sendsListItems = Sends.Select(s => new SendGroupingsPageListItem
{
Send = s,
ShowOptions = SendEnabled
}).ToList();
groupedSends.Add(new SendGroupingsPageListGroup(sendsListItems, groupedSends.Add(new SendGroupingsPageListGroup(sendsListItems,
MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count, MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count,
uppercaseGroupNames, !MainPage)); uppercaseGroupNames, !MainPage));

View File

@ -72,7 +72,8 @@
<DataTemplate x:DataType="views:SendView"> <DataTemplate x:DataType="views:SendView">
<controls:SendViewCell <controls:SendViewCell
Send="{Binding .}" Send="{Binding .}"
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}" /> ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>

View File

@ -15,6 +15,7 @@ namespace Bit.App.Pages
private readonly ISearchService _searchService; private readonly ISearchService _searchService;
private CancellationTokenSource _searchCancellationTokenSource; private CancellationTokenSource _searchCancellationTokenSource;
private bool _sendEnabled;
private bool _showNoData; private bool _showNoData;
private bool _showList; private bool _showList;
@ -29,6 +30,12 @@ namespace Bit.App.Pages
public ExtendedObservableCollection<SendView> Sends { get; set; } public ExtendedObservableCollection<SendView> Sends { get; set; }
public Func<SendView, bool> Filter { get; set; } public Func<SendView, bool> Filter { get; set; }
public bool SendEnabled
{
get => _sendEnabled;
set => SetProperty(ref _sendEnabled, value);
}
public bool ShowNoData public bool ShowNoData
{ {
get => _showNoData; get => _showNoData;
@ -51,6 +58,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text)) if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text))
{ {
Search((Page as SendsPage).SearchBar.Text, 200); Search((Page as SendsPage).SearchBar.Text, 200);

View File

@ -3375,6 +3375,12 @@ namespace Bit.App.Resources {
} }
} }
public static string SendLink {
get {
return ResourceManager.GetString("SendLink", resourceCulture);
}
}
public static string SearchSends { public static string SearchSends {
get { get {
return ResourceManager.GetString("SearchSends", resourceCulture); return ResourceManager.GetString("SearchSends", resourceCulture);
@ -3458,5 +3464,11 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("ShareOnSave", resourceCulture); return ResourceManager.GetString("ShareOnSave", resourceCulture);
} }
} }
public static string SendDisabledWarning {
get {
return ResourceManager.GetString("SendDisabledWarning", resourceCulture);
}
}
} }
} }

View File

@ -1907,8 +1907,12 @@
<data name="ShareLink" xml:space="preserve"> <data name="ShareLink" xml:space="preserve">
<value>Share Link</value> <value>Share Link</value>
</data> </data>
<data name="SendLink" xml:space="preserve">
<value>Send link</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="SearchSends" xml:space="preserve"> <data name="SearchSends" xml:space="preserve">
<value>Search sends</value> <value>Search Sends</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment> <comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data> </data>
<data name="EditSend" xml:space="preserve"> <data name="EditSend" xml:space="preserve">
@ -1957,4 +1961,8 @@
<value>Share this Send upon save.</value> <value>Share this Send upon save.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment> <comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data> </data>
<data name="SendDisabledWarning" xml:space="preserve">
<value>Due to an enterprise policy, you are only able to delete an existing Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
</root> </root>

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models; using Bit.App.Models;
using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
@ -157,13 +158,11 @@ namespace Bit.App.Utilities
} }
else if (selection == AppResources.CopyLink) else if (selection == AppResources.CopyLink)
{ {
await platformUtilsService.CopyToClipboardAsync(GetSendUrl(send)); await CopySendUrlAsync(send);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.ShareLink));
} }
else if (selection == AppResources.ShareLink) else if (selection == AppResources.ShareLink)
{ {
await ShareSendUrl(send); await ShareSendUrlAsync(send);
} }
else if (selection == AppResources.RemovePassword) else if (selection == AppResources.RemovePassword)
{ {
@ -176,14 +175,24 @@ namespace Bit.App.Utilities
return selection; return selection;
} }
public static string GetSendUrl(SendView send) public static async Task CopySendUrlAsync(SendView send)
{ {
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService"); if (await IsSendDisabledByPolicyAsync())
return environmentService.BaseUrl + "/#/send/" + send.AccessId + "/" + send.UrlB64Key; {
return;
}
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
await platformUtilsService.CopyToClipboardAsync(AppHelpers.GetSendUrl(send));
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
} }
public static async Task ShareSendUrl(SendView send) public static async Task ShareSendUrlAsync(SendView send)
{ {
if (await IsSendDisabledByPolicyAsync())
{
return;
}
await Share.RequestAsync(new ShareTextRequest await Share.RequestAsync(new ShareTextRequest
{ {
Uri = new Uri(GetSendUrl(send)).ToString(), Uri = new Uri(GetSendUrl(send)).ToString(),
@ -191,9 +200,19 @@ namespace Bit.App.Utilities
Subject = send.Name Subject = send.Name
}); });
} }
private static string GetSendUrl(SendView send)
{
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
return environmentService.BaseUrl + "/#/send/" + send.AccessId + "/" + send.UrlB64Key;
}
public static async Task<bool> RemoveSendPasswordAsync(string sendId) public static async Task<bool> RemoveSendPasswordAsync(string sendId)
{ {
if (await IsSendDisabledByPolicyAsync())
{
return false;
}
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
var sendService = ServiceContainer.Resolve<ISendService>("sendService"); var sendService = ServiceContainer.Resolve<ISendService>("sendService");
@ -270,6 +289,23 @@ namespace Bit.App.Utilities
return false; return false;
} }
public static async Task<bool> IsSendDisabledByPolicyAsync()
{
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
var userService = ServiceContainer.Resolve<IUserService>("userService");
var policies = await policyService.GetAll(PolicyType.DisableSend);
var organizations = await userService.GetAllOrganizationAsync();
return organizations.Any(o =>
{
return o.Enabled &&
o.Status == OrganizationUserStatusType.Confirmed &&
o.UsePolicies &&
!o.canManagePolicies &&
policies.Any(p => p.OrganizationId == o.Id && p.Enabled);
});
}
public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService, public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService,
IDeviceActionService deviceActionService, IStorageService storageService) IDeviceActionService deviceActionService, IStorageService storageService)
{ {

View File

@ -2,11 +2,12 @@
{ {
public enum PolicyType : byte public enum PolicyType : byte
{ {
TwoFactorAuthentication = 0, TwoFactorAuthentication = 0, // Requires users to have 2fa enabled
MasterPassword = 1, MasterPassword = 1, // Sets minimum requirements for master password complexity
PasswordGenerator = 2, PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases
OnlyOrg = 3, OnlyOrg = 3, // Allows users to only be apart of one organization
RequireSso = 4, RequireSso = 4, // Requires users to authenticate with SSO
PersonalOwnership = 5, PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
DisableSend = 6, // Disables the ability to create and edit Sends
} }
} }