mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-26 12:16:07 +01:00
[EC-259] Added Account Switching to Share extension on iOS (#1971)
* EC-259 Added Account switching on share extension on iOS, also improved performance for this and exception handling * EC-259 code formatting * EC-259 Added account switching to Share extension Send view * EC-259 Fixed navigation on share extension when a forms page is already presented * EC-259 Fix send text UI update when going from the iOS extension * EC-259 Improved DateTimeViewModel with helper property to easily setup date and time at the same time and applied on usage
This commit is contained in:
parent
d621a5d2f3
commit
292908f53f
@ -20,6 +20,7 @@ using System.Net;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.App.Controls;
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
@ -69,7 +70,8 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
|
ServiceContainer.Resolve<ILogger>("logger"));
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
@ -160,6 +162,7 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
|
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
|
@ -129,12 +129,10 @@
|
|||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
<Folder Include="Utilities\AccountManagement\" />
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
|
<Folder Include="Controls\DateTime\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
|
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -162,12 +160,6 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Update="Styles\Base.xaml">
|
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
||||||
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
||||||
@ -422,5 +414,6 @@
|
|||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
<None Remove="Utilities\AccountManagement\" />
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
|
<None Remove="Controls\DateTime\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -13,7 +13,8 @@ namespace Bit.App.Controls
|
|||||||
public AccountViewCellViewModel(AccountView accountView)
|
public AccountViewCellViewModel(AccountView accountView)
|
||||||
{
|
{
|
||||||
AccountView = accountView;
|
AccountView = accountView;
|
||||||
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
|
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
||||||
|
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountView AccountView
|
public AccountView AccountView
|
||||||
|
@ -50,7 +50,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
private Stream Draw()
|
private Stream Draw()
|
||||||
{
|
{
|
||||||
string chars = null;
|
string chars;
|
||||||
string upperData = null;
|
string upperData = null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_data))
|
if (string.IsNullOrEmpty(_data))
|
||||||
@ -71,30 +71,39 @@ namespace Bit.App.Controls
|
|||||||
var textColor = Color.White;
|
var textColor = Color.White;
|
||||||
var size = 50;
|
var size = 50;
|
||||||
|
|
||||||
var bitmap = new SKBitmap(
|
using (var bitmap = new SKBitmap(size * 2,
|
||||||
size * 2,
|
|
||||||
size * 2,
|
size * 2,
|
||||||
SKImageInfo.PlatformColorType,
|
SKImageInfo.PlatformColorType,
|
||||||
SKAlphaType.Premul);
|
SKAlphaType.Premul))
|
||||||
var canvas = new SKCanvas(bitmap);
|
{
|
||||||
|
using (var canvas = new SKCanvas(bitmap))
|
||||||
|
{
|
||||||
canvas.Clear(SKColors.Transparent);
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
using (var paint = new SKPaint
|
||||||
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
|
||||||
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
|
|
||||||
var radius = midX - midX / 5;
|
|
||||||
|
|
||||||
var circlePaint = new SKPaint
|
|
||||||
{
|
{
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
StrokeJoin = SKStrokeJoin.Miter,
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
Color = SKColor.Parse(bgColor.ToHex())
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
};
|
})
|
||||||
|
{
|
||||||
|
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
||||||
|
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
|
||||||
|
var radius = midX - midX / 5;
|
||||||
|
|
||||||
|
using (var circlePaint = new SKPaint
|
||||||
|
{
|
||||||
|
IsAntialias = true,
|
||||||
|
Style = SKPaintStyle.Fill,
|
||||||
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
|
})
|
||||||
|
{
|
||||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||||
|
|
||||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||||
var textSize = midX / 1.3f;
|
var textSize = midX / 1.3f;
|
||||||
var textPaint = new SKPaint
|
using (var textPaint = new SKPaint
|
||||||
{
|
{
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
@ -102,12 +111,22 @@ namespace Bit.App.Controls
|
|||||||
TextSize = textSize,
|
TextSize = textSize,
|
||||||
TextAlign = SKTextAlign.Center,
|
TextAlign = SKTextAlign.Center,
|
||||||
Typeface = typeface
|
Typeface = typeface
|
||||||
};
|
})
|
||||||
|
{
|
||||||
var rect = new SKRect();
|
var rect = new SKRect();
|
||||||
textPaint.MeasureText(chars, ref rect);
|
textPaint.MeasureText(chars, ref rect);
|
||||||
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
|
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
|
||||||
|
|
||||||
return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream();
|
using (var img = SKImage.FromBitmap(bitmap))
|
||||||
|
{
|
||||||
|
var data = img.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
return data?.AsStream(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFirstLetters(string data, int charCount)
|
private string GetFirstLetters(string data, int charCount)
|
||||||
|
33
src/App/Controls/AvatarImageSourcePool.cs
Normal file
33
src/App/Controls/AvatarImageSourcePool.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public interface IAvatarImageSourcePool
|
||||||
|
{
|
||||||
|
AvatarImageSource GetOrCreateAvatar(string name, string email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AvatarImageSourcePool : IAvatarImageSourcePool
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
|
||||||
|
|
||||||
|
public AvatarImageSource GetOrCreateAvatar(string name, string email)
|
||||||
|
{
|
||||||
|
var key = $"{name}{email}";
|
||||||
|
if (!_cache.TryGetValue(key, out var avatar))
|
||||||
|
{
|
||||||
|
avatar = new AvatarImageSource(name, email);
|
||||||
|
if (!_cache.TryAdd(key, avatar)
|
||||||
|
&&
|
||||||
|
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.
|
||||||
|
{
|
||||||
|
// if add and get after fails, then something wrong is going on with this method.
|
||||||
|
throw new InvalidOperationException("Something is wrong creating the avatar image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
src/App/Controls/DateTime/DateTimePicker.xaml
Normal file
20
src/App/Controls/DateTime/DateTimePicker.xaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Grid
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
x:Class="Bit.App.Controls.DateTimePicker"
|
||||||
|
ColumnDefinitions="*,*">
|
||||||
|
<controls:ExtendedDatePicker
|
||||||
|
x:Name="_datePicker"
|
||||||
|
Grid.Column="0"
|
||||||
|
NullableDate="{Binding Date, Mode=TwoWay}"
|
||||||
|
Format="d"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True" />
|
||||||
|
<controls:ExtendedTimePicker
|
||||||
|
x:Name="_timePicker"
|
||||||
|
Grid.Column="1"
|
||||||
|
NullableTime="{Binding Time, Mode=TwoWay}"
|
||||||
|
Format="t"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True" />
|
||||||
|
</Grid>
|
34
src/App/Controls/DateTime/DateTimePicker.xaml.cs
Normal file
34
src/App/Controls/DateTime/DateTimePicker.xaml.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Xamarin.CommunityToolkit.UI.Views;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public partial class DateTimePicker : Grid
|
||||||
|
{
|
||||||
|
public DateTimePicker()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(propertyName);
|
||||||
|
|
||||||
|
if (propertyName == nameof(BindingContext)
|
||||||
|
&&
|
||||||
|
BindingContext is DateTimeViewModel dateTimeViewModel)
|
||||||
|
{
|
||||||
|
AutomationProperties.SetName(_datePicker, dateTimeViewModel.DateName);
|
||||||
|
AutomationProperties.SetName(_timePicker, dateTimeViewModel.TimeName);
|
||||||
|
|
||||||
|
_datePicker.PlaceHolder = dateTimeViewModel.DatePlaceholder;
|
||||||
|
_timePicker.PlaceHolder = dateTimeViewModel.TimePlaceholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LazyDateTimePicker : LazyView<DateTimePicker>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
70
src/App/Controls/DateTime/DateTimeViewModel.cs
Normal file
70
src/App/Controls/DateTime/DateTimeViewModel.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public class DateTimeViewModel : ExtendedViewModel
|
||||||
|
{
|
||||||
|
DateTime? _date;
|
||||||
|
TimeSpan? _time;
|
||||||
|
|
||||||
|
public DateTimeViewModel(string dateName, string timeName)
|
||||||
|
{
|
||||||
|
DateName = dateName;
|
||||||
|
TimeName = timeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<DateTime?> OnDateChanged { get; set; }
|
||||||
|
public Action<TimeSpan?> OnTimeChanged { get; set; }
|
||||||
|
|
||||||
|
public DateTime? Date
|
||||||
|
{
|
||||||
|
get => _date;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _date, value))
|
||||||
|
{
|
||||||
|
OnDateChanged?.Invoke(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public TimeSpan? Time
|
||||||
|
{
|
||||||
|
get => _time;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _time, value))
|
||||||
|
{
|
||||||
|
OnTimeChanged?.Invoke(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DateName { get; }
|
||||||
|
public string TimeName { get; }
|
||||||
|
|
||||||
|
public string DatePlaceholder { get; set; }
|
||||||
|
public string TimePlaceholder { get; set; }
|
||||||
|
|
||||||
|
public DateTime? DateTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Date.HasValue)
|
||||||
|
{
|
||||||
|
if (Time.HasValue)
|
||||||
|
{
|
||||||
|
return Date.Value.Add(Time.Value);
|
||||||
|
}
|
||||||
|
return Date;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Date = value?.Date;
|
||||||
|
Time = value?.Date.TimeOfDay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@ -303,14 +303,14 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
|
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
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 DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
@ -343,7 +343,7 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
|
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
|
||||||
PlaceHolder="mm/dd/yyyy"
|
PlaceHolder="mm/dd/yyyy"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
@ -351,7 +351,7 @@
|
|||||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
|
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
PlaceHolder="--:-- --"
|
PlaceHolder="--:-- --"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
@ -23,7 +23,6 @@ namespace Bit.App.Pages
|
|||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
private SendAddEditPageViewModel _vm;
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
public Action OnClose { get; set; }
|
|
||||||
public Action AfterSubmit { get; set; }
|
public Action AfterSubmit { get; set; }
|
||||||
|
|
||||||
public SendAddEditPage(
|
public SendAddEditPage(
|
||||||
@ -135,16 +134,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CloseAsync()
|
private async Task CloseAsync()
|
||||||
{
|
|
||||||
if (OnClose is null)
|
|
||||||
{
|
{
|
||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
OnClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
@ -23,7 +24,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private bool _sendEnabled;
|
private bool _sendEnabled = true;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
private bool _emailVerified;
|
private bool _emailVerified;
|
||||||
private SendView _send;
|
private SendView _send;
|
||||||
@ -33,11 +34,7 @@ namespace Bit.App.Pages
|
|||||||
private int _deletionDateTypeSelectedIndex;
|
private int _deletionDateTypeSelectedIndex;
|
||||||
private int _expirationDateTypeSelectedIndex;
|
private int _expirationDateTypeSelectedIndex;
|
||||||
private DateTime _simpleDeletionDateTime;
|
private DateTime _simpleDeletionDateTime;
|
||||||
private DateTime _deletionDate;
|
|
||||||
private TimeSpan _deletionTime;
|
|
||||||
private DateTime? _simpleExpirationDateTime;
|
private DateTime? _simpleExpirationDateTime;
|
||||||
private DateTime? _expirationDate;
|
|
||||||
private TimeSpan? _expirationTime;
|
|
||||||
private bool _isOverridingPickers;
|
private bool _isOverridingPickers;
|
||||||
private int? _maxAccessCount;
|
private int? _maxAccessCount;
|
||||||
private string[] _additionalSendProperties = new[]
|
private string[] _additionalSendProperties = new[]
|
||||||
@ -89,8 +86,34 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
||||||
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
|
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DeletionDateTimeViewModel = new DateTimeViewModel(AppResources.DeletionDate, AppResources.DeletionTime);
|
||||||
|
ExpirationDateTimeViewModel = new DateTimeViewModel(AppResources.ExpirationDate, AppResources.ExpirationTime)
|
||||||
|
{
|
||||||
|
OnDateChanged = date =>
|
||||||
|
{
|
||||||
|
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Time.HasValue)
|
||||||
|
{
|
||||||
|
// auto-set time to current time upon setting date
|
||||||
|
ExpirationDateTimeViewModel.Time = DateTimeNow().TimeOfDay;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnTimeChanged = time =>
|
||||||
|
{
|
||||||
|
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Date.HasValue)
|
||||||
|
{
|
||||||
|
// auto-set date to current date upon setting time
|
||||||
|
ExpirationDateTimeViewModel.Date = DateTime.Today;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DatePlaceholder = "mm/dd/yyyy",
|
||||||
|
TimePlaceholder = "--:-- --"
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
public Command ToggleOptionsCommand { get; set; }
|
public Command ToggleOptionsCommand { get; set; }
|
||||||
public string SendId { get; set; }
|
public string SendId { get; set; }
|
||||||
@ -126,23 +149,14 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DateTime DeletionDate
|
|
||||||
{
|
|
||||||
get => _deletionDate;
|
|
||||||
set => SetProperty(ref _deletionDate, value);
|
|
||||||
}
|
|
||||||
public TimeSpan DeletionTime
|
|
||||||
{
|
|
||||||
get => _deletionTime;
|
|
||||||
set => SetProperty(ref _deletionTime, value);
|
|
||||||
}
|
|
||||||
public bool ShowOptions
|
public bool ShowOptions
|
||||||
{
|
{
|
||||||
get => _showOptions;
|
get => _showOptions;
|
||||||
set => SetProperty(ref _showOptions, value,
|
set => SetProperty(ref _showOptions, value,
|
||||||
additionalPropertyNames: new[]
|
additionalPropertyNames: new[]
|
||||||
{
|
{
|
||||||
nameof(OptionsAccessilibityText)
|
nameof(OptionsAccessilibityText),
|
||||||
|
nameof(OptionsShowHideIcon)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public int ExpirationDateTypeSelectedIndex
|
public int ExpirationDateTypeSelectedIndex
|
||||||
@ -156,28 +170,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DateTime? ExpirationDate
|
|
||||||
{
|
|
||||||
get => _expirationDate;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _expirationDate, value))
|
|
||||||
{
|
|
||||||
ExpirationDateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public TimeSpan? ExpirationTime
|
|
||||||
{
|
|
||||||
get => _expirationTime;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _expirationTime, value))
|
|
||||||
{
|
|
||||||
ExpirationTimeChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public int? MaxAccessCount
|
public int? MaxAccessCount
|
||||||
{
|
{
|
||||||
get => _maxAccessCount;
|
get => _maxAccessCount;
|
||||||
@ -205,7 +198,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
public string FileName
|
public string FileName
|
||||||
{
|
{
|
||||||
get => _fileName;
|
get => _fileName ?? AppResources.NoFileChosen;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _fileName, value))
|
if (SetProperty(ref _fileName, value))
|
||||||
@ -240,10 +233,13 @@ namespace Bit.App.Pages
|
|||||||
public bool IsFile => Send?.Type == SendType.File;
|
public bool IsFile => Send?.Type == SendType.File;
|
||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
|
public DateTimeViewModel DeletionDateTimeViewModel { get; }
|
||||||
|
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
|
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@ -268,10 +264,8 @@ namespace Bit.App.Pages
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Send = await send.DecryptAsync();
|
Send = await send.DecryptAsync();
|
||||||
DeletionDate = Send.DeletionDate.ToLocalTime();
|
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
|
||||||
DeletionTime = DeletionDate.TimeOfDay;
|
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
|
||||||
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
|
|
||||||
ExpirationTime = ExpirationDate?.TimeOfDay;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -280,8 +274,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Type = Type.GetValueOrDefault(defaultType),
|
Type = Type.GetValueOrDefault(defaultType),
|
||||||
};
|
};
|
||||||
_deletionDate = DateTimeNow().AddDays(7);
|
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
|
||||||
_deletionTime = DeletionDate.TimeOfDay;
|
|
||||||
DeletionDateTypeSelectedIndex = 4;
|
DeletionDateTypeSelectedIndex = 4;
|
||||||
ExpirationDateTypeSelectedIndex = 0;
|
ExpirationDateTypeSelectedIndex = 0;
|
||||||
}
|
}
|
||||||
@ -305,23 +298,22 @@ namespace Bit.App.Pages
|
|||||||
public void ClearExpirationDate()
|
public void ClearExpirationDate()
|
||||||
{
|
{
|
||||||
_isOverridingPickers = true;
|
_isOverridingPickers = true;
|
||||||
ExpirationDate = null;
|
ExpirationDateTimeViewModel.DateTime = null;
|
||||||
ExpirationTime = null;
|
|
||||||
_isOverridingPickers = false;
|
_isOverridingPickers = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSendData()
|
private void UpdateSendData()
|
||||||
{
|
{
|
||||||
// filename
|
// filename
|
||||||
if (Send.File != null && FileName != null)
|
if (Send.File != null && _fileName != null)
|
||||||
{
|
{
|
||||||
Send.File.FileName = FileName;
|
Send.File.FileName = _fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletion date
|
// deletion date
|
||||||
if (ShowDeletionCustomPickers)
|
if (ShowDeletionCustomPickers)
|
||||||
{
|
{
|
||||||
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
|
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -329,9 +321,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expiration date
|
// expiration date
|
||||||
if (ShowExpirationCustomPickers && ExpirationDate.HasValue && ExpirationTime.HasValue)
|
if (ShowExpirationCustomPickers && ExpirationDateTimeViewModel.DateTime.HasValue)
|
||||||
{
|
{
|
||||||
Send.ExpirationDate = ExpirationDate.Value.Date.Add(ExpirationTime.Value).ToUniversalTime();
|
Send.ExpirationDate = ExpirationDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
||||||
}
|
}
|
||||||
else if (_simpleExpirationDateTime.HasValue)
|
else if (_simpleExpirationDateTime.HasValue)
|
||||||
{
|
{
|
||||||
@ -484,7 +476,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
|
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
|
||||||
{
|
{
|
||||||
sendPage.OnClose();
|
sendPage.OnClose();
|
||||||
return;
|
return;
|
||||||
@ -625,24 +617,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExpirationDateChanged()
|
|
||||||
{
|
|
||||||
if (!_isOverridingPickers && !ExpirationTime.HasValue)
|
|
||||||
{
|
|
||||||
// auto-set time to current time upon setting date
|
|
||||||
ExpirationTime = DateTimeNow().TimeOfDay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpirationTimeChanged()
|
|
||||||
{
|
|
||||||
if (!_isOverridingPickers && !ExpirationDate.HasValue)
|
|
||||||
{
|
|
||||||
// auto-set date to current date upon setting time
|
|
||||||
ExpirationDate = DateTime.Today;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MaxAccessCountChanged()
|
private void MaxAccessCountChanged()
|
||||||
{
|
{
|
||||||
Send.MaxAccessCount = _maxAccessCount;
|
Send.MaxAccessCount = _maxAccessCount;
|
||||||
@ -666,5 +640,10 @@ namespace Bit.App.Pages
|
|||||||
DateTimeKind.Local
|
DateTimeKind.Local
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void TriggerSendTextPropertyChanged()
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Send)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
183
src/App/Pages/Send/SendAddOnlyOptionsView.xaml
Normal file
183
src/App/Pages/Send/SendAddOnlyOptionsView.xaml
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<ContentView
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
|
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||||
|
x:DataType="pages:SendAddEditPageViewModel"
|
||||||
|
x:Class="Bit.App.Pages.SendAddOnlyOptionsView">
|
||||||
|
<ContentView.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentView.Resources>
|
||||||
|
<ContentView.Content>
|
||||||
|
<StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DeletionDate}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Picker
|
||||||
|
x:Name="_deletionDateTypePicker"
|
||||||
|
ItemsSource="{Binding DeletionTypeOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding DeletionDateTypeSelectedIndex}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
ItemDisplayBinding="{Binding Key}"
|
||||||
|
ios:Picker.UpdateMode="WhenFinished"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n DeletionTime}" />
|
||||||
|
<controls:LazyDateTimePicker
|
||||||
|
x:Name="_lazyDeletionDateTimePicker"
|
||||||
|
BindingContext="{Binding DeletionDateTimeViewModel}"
|
||||||
|
IsVisible="{Binding ShowDeletionCustomPickers}"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DeletionDateInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout StyleClass="box-row" Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ExpirationDate}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Picker
|
||||||
|
x:Name="_expirationDateTypePicker"
|
||||||
|
ItemsSource="{Binding ExpirationTypeOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding ExpirationDateTypeSelectedIndex}"
|
||||||
|
ItemDisplayBinding="{Binding Key}"
|
||||||
|
ios:Picker.UpdateMode="WhenFinished"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ExpirationTime}" />
|
||||||
|
<controls:LazyDateTimePicker
|
||||||
|
x:Name="_lazyExpirationDateTimePicker"
|
||||||
|
BindingContext="{Binding ExpirationDateTimeViewModel}"
|
||||||
|
IsVisible="{Binding ShowExpirationCustomPickers}"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ExpirationDateInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MaximumAccessCount}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Entry
|
||||||
|
Text="{Binding MaxAccessCount}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Keyboard="Numeric"
|
||||||
|
MaxLength="9"
|
||||||
|
TextChanged="OnMaxAccessCountTextChanged"
|
||||||
|
HorizontalOptions="FillAndExpand" />
|
||||||
|
<controls:ExtendedStepper
|
||||||
|
x:Name="_maxAccessCountStepper"
|
||||||
|
Value="{Binding MaxAccessCount}"
|
||||||
|
Maximum="999999999"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MaximumAccessCountInfo}"
|
||||||
|
StyleClass="box-footer-label" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NewPassword}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout Orientation="Horizontal">
|
||||||
|
<Entry
|
||||||
|
Text="{Binding NewPassword}"
|
||||||
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
HorizontalOptions="FillAndExpand" />
|
||||||
|
<controls:IconButton
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowPasswordIcon}"
|
||||||
|
Command="{Binding TogglePasswordCommand}"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Notes}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Editor
|
||||||
|
x:Name="_notesEditor"
|
||||||
|
AutoSize="TextChanges"
|
||||||
|
Text="{Binding Send.Notes}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Margin="0,10,0,5"
|
||||||
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
|
<Editor.Effects>
|
||||||
|
<effects:ScrollEnabledEffect />
|
||||||
|
</Editor.Effects>
|
||||||
|
</Editor>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="box-row-separator" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NotesInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n HideEmail}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.HideEmail}"
|
||||||
|
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DisableSend}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.Disabled}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</ContentView.Content>
|
||||||
|
</ContentView>
|
91
src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
Normal file
91
src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Behaviors;
|
||||||
|
using Xamarin.CommunityToolkit.UI.Views;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class SendAddOnlyOptionsView : ContentView
|
||||||
|
{
|
||||||
|
public SendAddOnlyOptionsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendAddEditPageViewModel ViewModel => BindingContext as SendAddEditPageViewModel;
|
||||||
|
|
||||||
|
public void SetMainScrollView(ScrollView scrollView)
|
||||||
|
{
|
||||||
|
_notesEditor.Behaviors.Add(new EditorPreventAutoBottomScrollingOnFocusedBehavior { ParentScrollView = scrollView });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMaxAccessCountTextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(e.NewTextValue))
|
||||||
|
{
|
||||||
|
ViewModel.MaxAccessCount = null;
|
||||||
|
_maxAccessCountStepper.Value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// accept only digits
|
||||||
|
if (!int.TryParse(e.NewTextValue, out int _))
|
||||||
|
{
|
||||||
|
((Entry)sender).Text = e.OldTextValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(propertyName);
|
||||||
|
|
||||||
|
if (propertyName == nameof(BindingContext)
|
||||||
|
&&
|
||||||
|
ViewModel != null)
|
||||||
|
{
|
||||||
|
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_lazyDeletionDateTimePicker.IsLoaded
|
||||||
|
&&
|
||||||
|
e.PropertyName == nameof(SendAddEditPageViewModel.ShowDeletionCustomPickers)
|
||||||
|
&&
|
||||||
|
ViewModel.ShowDeletionCustomPickers)
|
||||||
|
{
|
||||||
|
_lazyDeletionDateTimePicker.LoadViewAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lazyExpirationDateTimePicker.IsLoaded
|
||||||
|
&&
|
||||||
|
e.PropertyName == nameof(SendAddEditPageViewModel.ShowExpirationCustomPickers)
|
||||||
|
&&
|
||||||
|
ViewModel.ShowExpirationCustomPickers)
|
||||||
|
{
|
||||||
|
_lazyExpirationDateTimePicker.LoadViewAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SendAddOnlyOptionsLazyView : LazyView<SendAddOnlyOptionsView>
|
||||||
|
{
|
||||||
|
public ScrollView MainScrollView { get; set; }
|
||||||
|
|
||||||
|
public override async ValueTask LoadViewAsync()
|
||||||
|
{
|
||||||
|
await base.LoadViewAsync();
|
||||||
|
|
||||||
|
if (Content is SendAddOnlyOptionsView optionsView)
|
||||||
|
{
|
||||||
|
optionsView.SetMainScrollView(MainScrollView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
190
src/App/Pages/Send/SendAddOnlyPage.xaml
Normal file
190
src/App/Pages/Send/SendAddOnlyPage.xaml
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<?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.SendAddOnlyPage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
|
||||||
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
|
x:DataType="pages:SendAddEditPageViewModel"
|
||||||
|
x:Name="_page"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:SendAddEditPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<!--Order matters here or the avatar's image won't be updated correctly, check iOS CustomNavigationRenderer for more info-->
|
||||||
|
<controls:ExtendedToolbarItem
|
||||||
|
x:Name="_accountAvatar"
|
||||||
|
IconImageSource="{Binding AvatarImageSource}"
|
||||||
|
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
|
||||||
|
Order="Primary"
|
||||||
|
Priority="-2"
|
||||||
|
UseOriginalImage="True"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
||||||
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<AbsoluteLayout>
|
||||||
|
<ScrollView
|
||||||
|
x:Name="_scrollView"
|
||||||
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
|
AbsoluteLayout.LayoutFlags="All">
|
||||||
|
<StackLayout x:Name="_mainContainer" 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>
|
||||||
|
<Frame
|
||||||
|
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n SendOptionsPolicyInEffect}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</Frame>
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Name}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Entry
|
||||||
|
x:Name="_nameEntry"
|
||||||
|
Text="{Binding Send.Name}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NameInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
IsVisible="{Binding IsFile}">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeFile}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{Binding FileName}"
|
||||||
|
LineBreakMode="CharacterWrap"
|
||||||
|
StyleClass="text-sm, text-muted"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
<Label
|
||||||
|
Margin="0, 5, 0, 0"
|
||||||
|
Text="{u:I18n MaxFileSize}"
|
||||||
|
StyleClass="text-sm, text-muted"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
IsVisible="{Binding IsText}">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeText}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Editor
|
||||||
|
x:Name="_textEditor"
|
||||||
|
AutoSize="TextChanges"
|
||||||
|
Text="{Binding Send.Text.Text}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Margin="{Binding EditorMargins}"
|
||||||
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
|
<Editor.Behaviors>
|
||||||
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
|
</Editor.Behaviors>
|
||||||
|
<Editor.Effects>
|
||||||
|
<effects:ScrollEnabledEffect />
|
||||||
|
</Editor.Effects>
|
||||||
|
</Editor>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="box-row-separator" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeTextInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,10" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n HideTextByDefault}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.Text.Hidden}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch">
|
||||||
|
<Label
|
||||||
|
Text="{Binding ShareOnSaveText}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding ShareOnSave}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
|
||||||
|
<StackLayout.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Tapped="OptionsHeader_Tapped" />
|
||||||
|
</StackLayout.GestureRecognizers>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Options}"
|
||||||
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
|
Margin="0,0,5,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
|
<controls:IconLabel
|
||||||
|
Text="{Binding OptionsShowHideIcon}"
|
||||||
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
|
</StackLayout>
|
||||||
|
<pages:SendAddOnlyOptionsLazyView x:Name="_lazyOptionsView" IsVisible="{Binding ShowOptions}" />
|
||||||
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<controls:AccountSwitchingOverlayView
|
||||||
|
x:Name="_accountListOverlay"
|
||||||
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
LongPressAccountEnabled="False"
|
||||||
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
</pages:BaseContentPage>
|
178
src/App/Pages/Send/SendAddOnlyPage.xaml.cs
Normal file
178
src/App/Pages/Send/SendAddOnlyPage.xaml.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a version of <see cref="SendAddEditPage"/> that is reduced for adding only and adapted
|
||||||
|
/// for performance for iOS Share extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should NOT be used in Android.
|
||||||
|
/// </remarks>
|
||||||
|
public partial class SendAddOnlyPage : BaseContentPage
|
||||||
|
{
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
|
private AppOptions _appOptions;
|
||||||
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
|
public Action OnClose { get; set; }
|
||||||
|
public Action AfterSubmit { get; set; }
|
||||||
|
|
||||||
|
public SendAddOnlyPage(
|
||||||
|
AppOptions appOptions = null,
|
||||||
|
string sendId = null,
|
||||||
|
SendType? type = null)
|
||||||
|
{
|
||||||
|
if (appOptions?.IosExtension != true)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(nameof(SendAddOnlyPage) + " is only prepared to be used in iOS share extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_appOptions = appOptions;
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as SendAddEditPageViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.SendId = sendId;
|
||||||
|
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
|
||||||
|
|
||||||
|
if (_vm.IsText)
|
||||||
|
{
|
||||||
|
_nameEntry.ReturnType = ReturnType.Next;
|
||||||
|
_nameEntry.ReturnCommand = new Command(() => _textEditor.Focus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
|
||||||
|
{
|
||||||
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
|
}
|
||||||
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _vm.InitAsync();
|
||||||
|
|
||||||
|
if (!await _vm.LoadAsync())
|
||||||
|
{
|
||||||
|
await CloseAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountAvatar?.OnAppearing();
|
||||||
|
await Device.InvokeOnMainThreadAsync(async () => _vm.AvatarImageSource = await GetAvatarImageSourceAsync());
|
||||||
|
|
||||||
|
await HandleCreateRequest();
|
||||||
|
if (string.IsNullOrWhiteSpace(_vm.Send?.Name))
|
||||||
|
{
|
||||||
|
RequestFocus(_nameEntry);
|
||||||
|
}
|
||||||
|
AdjustToolbar();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
await CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_accountAvatar?.OnDisappearing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CloseAsync()
|
||||||
|
{
|
||||||
|
if (OnClose is null)
|
||||||
|
{
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Save_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
var submitted = await _vm.SubmitAsync();
|
||||||
|
if (submitted)
|
||||||
|
{
|
||||||
|
AfterSubmit?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdjustToolbar()
|
||||||
|
{
|
||||||
|
_saveItem.IsEnabled = _vm.SendEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task HandleCreateRequest()
|
||||||
|
{
|
||||||
|
if (_appOptions?.CreateSend == null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vm.IsAddFromShare = true;
|
||||||
|
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
|
||||||
|
|
||||||
|
var name = _appOptions.CreateSend.Item2;
|
||||||
|
_vm.Send.Name = name;
|
||||||
|
|
||||||
|
var type = _appOptions.CreateSend.Item1;
|
||||||
|
if (type == SendType.File)
|
||||||
|
{
|
||||||
|
_vm.FileData = _appOptions.CreateSend.Item3;
|
||||||
|
_vm.FileName = name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var text = _appOptions.CreateSend.Item4;
|
||||||
|
_vm.Send.Text.Text = text;
|
||||||
|
_vm.TriggerSendTextPropertyChanged();
|
||||||
|
}
|
||||||
|
_appOptions.CreateSend = null;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OptionsHeader_Tapped(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_vm.ToggleOptionsCommand.Execute(null);
|
||||||
|
|
||||||
|
if (!_lazyOptionsView.IsLoaded)
|
||||||
|
{
|
||||||
|
_lazyOptionsView.MainScrollView = _scrollView;
|
||||||
|
_lazyOptionsView.LoadViewAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
Func<AppOptions> _getOptionsFunc;
|
Func<AppOptions> _getOptionsFunc;
|
||||||
private IAccountsManagerHost _accountsManagerHost;
|
private IAccountsManagerHost _accountsManagerHost;
|
||||||
@ -28,7 +29,8 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
IStorageService secureStorageService,
|
IStorageService secureStorageService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IPlatformUtilsService platformUtilsService,
|
IPlatformUtilsService platformUtilsService,
|
||||||
IAuthService authService)
|
IAuthService authService,
|
||||||
|
ILogger logger)
|
||||||
{
|
{
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
_vaultTimeoutService = vaultTimeoutService;
|
_vaultTimeoutService = vaultTimeoutService;
|
||||||
@ -36,6 +38,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_platformUtilsService = platformUtilsService;
|
_platformUtilsService = platformUtilsService;
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||||
@ -108,24 +111,26 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async void OnMessage(Message message)
|
private async void OnMessage(Message message)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
switch (message.Command)
|
switch (message.Command)
|
||||||
{
|
{
|
||||||
case AccountsManagerMessageCommands.LOCKED:
|
case AccountsManagerMessageCommands.LOCKED:
|
||||||
Locked(message.Data as Tuple<string, bool>);
|
await Device.InvokeOnMainThreadAsync(() => LockedAsync(message.Data as Tuple<string, bool>));
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.LOCK_VAULT:
|
case AccountsManagerMessageCommands.LOCK_VAULT:
|
||||||
await _vaultTimeoutService.LockAsync(true);
|
await _vaultTimeoutService.LockAsync(true);
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.LOGOUT:
|
case AccountsManagerMessageCommands.LOGOUT:
|
||||||
LogOut(message.Data as Tuple<string, bool, bool>);
|
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(message.Data as Tuple<string, bool, bool>));
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.LOGGED_OUT:
|
case AccountsManagerMessageCommands.LOGGED_OUT:
|
||||||
// Clean up old migrated key if they ever log out.
|
// Clean up old migrated key if they ever log out.
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
||||||
AddAccount();
|
await AddAccountAsync();
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
||||||
await _accountsManagerHost.UpdateThemeAsync();
|
await _accountsManagerHost.UpdateThemeAsync();
|
||||||
@ -135,16 +140,17 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Locked(Tuple<string, bool> extras)
|
private async Task LockedAsync(Tuple<string, bool> extras)
|
||||||
{
|
{
|
||||||
var userId = extras?.Item1;
|
var userId = extras?.Item1;
|
||||||
var userInitiated = extras?.Item2 ?? false;
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool userInitiated)
|
|
||||||
{
|
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||||
@ -163,28 +169,24 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
|
|
||||||
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
||||||
|
|
||||||
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
await Device.InvokeOnMainThreadAsync(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddAccount()
|
private async Task AddAccountAsync()
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
await Device.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
Options.HideAccountSwitcher = false;
|
Options.HideAccountSwitcher = false;
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogOut(Tuple<string, bool, bool> extras)
|
private async Task LogOutAsync(Tuple<string, bool, bool> extras)
|
||||||
{
|
{
|
||||||
var userId = extras?.Item1;
|
var userId = extras?.Item1;
|
||||||
var userInitiated = extras?.Item2 ?? true;
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
var expired = extras?.Item3 ?? false;
|
var expired = extras?.Item3 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
|
||||||
{
|
|
||||||
await AppHelpers.LogOutAsync(userId, userInitiated);
|
await AppHelpers.LogOutAsync(userId, userInitiated);
|
||||||
await NavigateOnAccountChangeAsync();
|
await NavigateOnAccountChangeAsync();
|
||||||
_authService.LogOut(() =>
|
_authService.LogOut(() =>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using UIKit;
|
|
||||||
using Foundation;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.App.Pages;
|
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Xamarin.Forms;
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
{
|
{
|
||||||
@ -39,6 +39,10 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
protected bool autofillExtension = false;
|
protected bool autofillExtension = false;
|
||||||
|
|
||||||
|
public BaseLockPasswordViewController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public BaseLockPasswordViewController(IntPtr handle)
|
public BaseLockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{ }
|
{ }
|
||||||
@ -168,12 +172,11 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||||
}
|
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
TableView.EstimatedRowHeight = 70;
|
TableView.EstimatedRowHeight = 70;
|
||||||
TableView.Source = new TableSource(this);
|
TableView.Source = new TableSource(this);
|
||||||
TableView.AllowsSelection = true;
|
TableView.AllowsSelection = true;
|
||||||
|
}
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ViewDidAppear(bool animated)
|
public override void ViewDidAppear(bool animated)
|
||||||
{
|
{
|
||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
@ -402,28 +405,43 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
MasterPasswordCell?.Dispose();
|
||||||
|
MasterPasswordCell = null;
|
||||||
|
|
||||||
|
TableView?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public class TableSource : ExtendedUITableViewSource
|
public class TableSource : ExtendedUITableViewSource
|
||||||
{
|
{
|
||||||
private readonly BaseLockPasswordViewController _controller;
|
private readonly WeakReference<BaseLockPasswordViewController> _controller;
|
||||||
|
|
||||||
public TableSource(BaseLockPasswordViewController controller)
|
public TableSource(BaseLockPasswordViewController controller)
|
||||||
{
|
{
|
||||||
_controller = controller;
|
_controller = new WeakReference<BaseLockPasswordViewController>(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
|
{
|
||||||
|
return new ExtendedUITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
if (indexPath.Section == 0)
|
if (indexPath.Section == 0)
|
||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (_controller._biometricUnlockOnly)
|
if (controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return _controller.BiometricCell;
|
return controller.BiometricCell;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return _controller.MasterPasswordCell;
|
return controller.MasterPasswordCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,7 +449,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (_controller._passwordReprompt)
|
if (controller._passwordReprompt)
|
||||||
{
|
{
|
||||||
var cell = new ExtendedUITableViewCell();
|
var cell = new ExtendedUITableViewCell();
|
||||||
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||||
@ -441,9 +459,9 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
else if (!_controller._biometricUnlockOnly)
|
else if (!controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return _controller.BiometricCell;
|
return controller.BiometricCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,8 +475,13 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
{
|
{
|
||||||
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
_controller._passwordReprompt
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
||||||
|
controller._passwordReprompt
|
||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
@ -484,13 +507,18 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
tableView.DeselectRow(indexPath, true);
|
||||||
tableView.EndEditing(true);
|
tableView.EndEditing(true);
|
||||||
if (indexPath.Row == 0 &&
|
if (indexPath.Row == 0 &&
|
||||||
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
((controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||||
indexPath.Section == 1))
|
indexPath.Section == 1))
|
||||||
{
|
{
|
||||||
var task = _controller.PromptBiometricAsync();
|
var task = controller.PromptBiometricAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cell = tableView.CellAt(indexPath);
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
@ -8,6 +8,10 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
public Action DismissModalAction { get; set; }
|
public Action DismissModalAction { get; set; }
|
||||||
|
|
||||||
|
public ExtendedUIViewController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public ExtendedUIViewController(IntPtr handle)
|
public ExtendedUIViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@ -28,16 +32,28 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
}
|
}
|
||||||
if (NavigationController?.NavigationBar != null)
|
UpdateNavigationBarTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateNavigationBarTheme()
|
||||||
{
|
{
|
||||||
NavigationController.NavigationBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
UpdateNavigationBarTheme(NavigationController?.NavigationBar);
|
||||||
NavigationController.NavigationBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
}
|
||||||
NavigationController.NavigationBar.TintColor = ThemeHelpers.NavBarTextColor;
|
|
||||||
NavigationController.NavigationBar.TitleTextAttributes = new UIStringAttributes
|
protected void UpdateNavigationBarTheme(UINavigationBar navBar)
|
||||||
|
{
|
||||||
|
if (navBar is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
||||||
|
navBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
||||||
|
navBar.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
|
navBar.TitleTextAttributes = new UIStringAttributes
|
||||||
{
|
{
|
||||||
ForegroundColor = ThemeHelpers.NavBarTextColor
|
ForegroundColor = ThemeHelpers.NavBarTextColor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -184,7 +184,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ViewDidAppear(bool animated)
|
public override void ViewDidAppear(bool animated)
|
||||||
{
|
{
|
||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
|
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
|
||||||
|
|
||||||
IStateService _stateService;
|
readonly IStateService _stateService;
|
||||||
IMessagingService _messagingService;
|
readonly IMessagingService _messagingService;
|
||||||
ILogger _logger;
|
readonly ILogger _logger;
|
||||||
|
|
||||||
public AccountSwitchingOverlayHelper()
|
public AccountSwitchingOverlayHelper()
|
||||||
{
|
{
|
||||||
@ -34,9 +34,11 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
}
|
}
|
||||||
|
|
||||||
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||||
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
|
using (var avatarUIImage = await avatarImageSource.GetNativeImageAsync())
|
||||||
|
{
|
||||||
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Exception(ex);
|
_logger.Exception(ex);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@ -15,6 +16,7 @@ using Bit.iOS.Core.Services;
|
|||||||
using CoreNFC;
|
using CoreNFC;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Utilities
|
namespace Bit.iOS.Core.Utilities
|
||||||
{
|
{
|
||||||
@ -26,6 +28,42 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public static string AppGroupId = "group.com.8bit.bitwarden";
|
public static string AppGroupId = "group.com.8bit.bitwarden";
|
||||||
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
|
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
|
||||||
|
|
||||||
|
public static void InitApp<T>(T rootController,
|
||||||
|
string clearCipherCacheKey,
|
||||||
|
NFCNdefReaderSession nfcSession,
|
||||||
|
out NFCReaderDelegate nfcDelegate,
|
||||||
|
out IAccountsManager accountsManager)
|
||||||
|
where T : UIViewController, IAccountsManagerHost
|
||||||
|
{
|
||||||
|
Forms.Init();
|
||||||
|
|
||||||
|
if (ServiceContainer.RegisteredServices.Count > 0)
|
||||||
|
{
|
||||||
|
ServiceContainer.Reset();
|
||||||
|
}
|
||||||
|
RegisterLocalServices();
|
||||||
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
||||||
|
clearCipherCacheKey,
|
||||||
|
Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||||
|
InitLogger();
|
||||||
|
Bootstrap();
|
||||||
|
|
||||||
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
|
var app = new App.App(appOptions);
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
|
||||||
|
AppearanceAdjustments();
|
||||||
|
|
||||||
|
nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||||
|
messagingService.Send("gotYubiKeyOTP", message));
|
||||||
|
SubscribeBroadcastReceiver(rootController, nfcSession, nfcDelegate);
|
||||||
|
|
||||||
|
accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
accountsManager.Init(() => appOptions, rootController);
|
||||||
|
}
|
||||||
|
|
||||||
public static void InitLogger()
|
public static void InitLogger()
|
||||||
{
|
{
|
||||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
@ -89,6 +127,7 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
|
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
||||||
@ -181,7 +220,8 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
|
ServiceContainer.Resolve<ILogger>("logger"));
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
|
|
||||||
if (postBootstrapFunc != null)
|
if (postBootstrapFunc != null)
|
||||||
|
27
src/iOS.ShareExtension/ExtensionNavigationController.cs
Normal file
27
src/iOS.ShareExtension/ExtensionNavigationController.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// This file has been autogenerated from a class added in the UI designer.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.ShareExtension
|
||||||
|
{
|
||||||
|
public partial class ExtensionNavigationController : UINavigationController
|
||||||
|
{
|
||||||
|
public ExtensionNavigationController (IntPtr handle) : base (handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UIViewController PopViewController(bool animated)
|
||||||
|
{
|
||||||
|
TopViewController?.Dispose();
|
||||||
|
return base.PopViewController(animated);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DismissModalViewController(bool animated)
|
||||||
|
{
|
||||||
|
ModalViewController?.Dispose();
|
||||||
|
base.DismissModalViewController(animated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/iOS.ShareExtension/ExtensionNavigationController.designer.cs
generated
Normal file
20
src/iOS.ShareExtension/ExtensionNavigationController.designer.cs
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// WARNING
|
||||||
|
//
|
||||||
|
// This file has been generated automatically by Visual Studio to store outlets and
|
||||||
|
// actions made in the UI designer. If it is removed, they will be lost.
|
||||||
|
// Manual changes to this file may not be handled correctly.
|
||||||
|
//
|
||||||
|
using Foundation;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
|
|
||||||
|
namespace Bit.iOS.ShareExtension
|
||||||
|
{
|
||||||
|
[Register ("ExtensionNavigationController")]
|
||||||
|
partial class ExtensionNavigationController
|
||||||
|
{
|
||||||
|
|
||||||
|
void ReleaseDesignerOutlets ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,11 @@ using Bit.App.Abstractions;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Core;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
using Bit.iOS.Core.Controllers;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
@ -24,16 +24,33 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.iOS.ShareExtension
|
namespace Bit.iOS.ShareExtension
|
||||||
{
|
{
|
||||||
public partial class LoadingViewController : ExtendedUIViewController
|
public partial class LoadingViewController : ExtendedUIViewController, IAccountsManagerHost
|
||||||
{
|
{
|
||||||
|
const string STORYBOARD_NAME = "MainInterface";
|
||||||
|
|
||||||
private Context _context = new Context();
|
private Context _context = new Context();
|
||||||
private NFCNdefReaderSession _nfcSession = null;
|
private NFCNdefReaderSession _nfcSession = null;
|
||||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||||
|
private IAccountsManager _accountsManager;
|
||||||
|
|
||||||
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||||
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>("deviceActionService");
|
|
||||||
readonly LazyResolve<IEventService> _eventService = new LazyResolve<IEventService>("eventService");
|
Lazy<UIStoryboard> _storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
|
||||||
|
|
||||||
|
private App.App _app = null;
|
||||||
|
private UIViewController _currentModalController;
|
||||||
|
private bool _presentingOnNavigationPage;
|
||||||
|
|
||||||
|
private ExtensionNavigationController ExtNavigationController
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
NavigationController.PresentationController.Delegate =
|
||||||
|
new CustomPresentationControllerDelegate(CompleteRequest);
|
||||||
|
return NavigationController as ExtensionNavigationController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public LoadingViewController(IntPtr handle)
|
public LoadingViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
@ -41,39 +58,38 @@ namespace Bit.iOS.ShareExtension
|
|||||||
|
|
||||||
public override void ViewDidLoad()
|
public override void ViewDidLoad()
|
||||||
{
|
{
|
||||||
InitApp();
|
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
|
_nfcSession, out _nfcDelegate, out _accountsManager);
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||||
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
||||||
_context.ExtensionContext = ExtensionContext;
|
_context.ExtensionContext = ExtensionContext;
|
||||||
|
_context.ProviderType = GetProviderTypeFromExtensionInputItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provider <see cref="UTType"/> given the input items
|
||||||
|
/// </summary>
|
||||||
|
private string GetProviderTypeFromExtensionInputItems()
|
||||||
|
{
|
||||||
foreach (var item in ExtensionContext.InputItems)
|
foreach (var item in ExtensionContext.InputItems)
|
||||||
{
|
{
|
||||||
var processed = false;
|
|
||||||
foreach (var itemProvider in item.Attachments)
|
foreach (var itemProvider in item.Attachments)
|
||||||
{
|
{
|
||||||
if (itemProvider.HasItemConformingTo(UTType.PlainText))
|
if (itemProvider.HasItemConformingTo(UTType.PlainText))
|
||||||
{
|
{
|
||||||
_context.ProviderType = UTType.PlainText;
|
return UTType.PlainText;
|
||||||
|
}
|
||||||
|
|
||||||
processed = true;
|
if (itemProvider.HasItemConformingTo(UTType.Data))
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (itemProvider.HasItemConformingTo(UTType.Data))
|
|
||||||
{
|
{
|
||||||
_context.ProviderType = UTType.Data;
|
return UTType.Data;
|
||||||
|
|
||||||
processed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (processed)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ViewDidAppear(bool animated)
|
public override async void ViewDidAppear(bool animated)
|
||||||
@ -84,12 +100,12 @@ namespace Bit.iOS.ShareExtension
|
|||||||
{
|
{
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchHomePage();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (await IsLocked())
|
else if (await IsLocked())
|
||||||
{
|
{
|
||||||
PerformSegue("lockPasswordSegue", this);
|
NavigateToLockViewController();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -102,24 +118,52 @@ namespace Bit.iOS.ShareExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
void NavigateToLockViewController()
|
||||||
{
|
{
|
||||||
if (segue.DestinationViewController is UINavigationController navController
|
var viewController = _storyboard.Value.InstantiateViewController("lockVC") as LockPasswordViewController;
|
||||||
&&
|
viewController.LoadingController = this;
|
||||||
navController.TopViewController is LockPasswordViewController passwordViewController)
|
viewController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
|
||||||
|
if (_presentingOnNavigationPage)
|
||||||
{
|
{
|
||||||
passwordViewController.LoadingController = this;
|
_presentingOnNavigationPage = false;
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
DismissViewController(true, () => ExtNavigationController.PushViewController(viewController, true));
|
||||||
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExtNavigationController.PushViewController(viewController, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DismissLockAndContinue()
|
public void DismissLockAndContinue()
|
||||||
{
|
{
|
||||||
Debug.WriteLine("BW Log, Dismissing lock controller.");
|
Debug.WriteLine("BW Log, Dismissing lock controller.");
|
||||||
|
|
||||||
|
ClearBeforeNavigating();
|
||||||
|
|
||||||
DismissViewController(false, () => ContinueOnAsync().FireAndForget());
|
DismissViewController(false, () => ContinueOnAsync().FireAndForget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DismissAndLaunch(Action pageToLaunch)
|
||||||
|
{
|
||||||
|
ClearBeforeNavigating();
|
||||||
|
|
||||||
|
DismissViewController(false, pageToLaunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBeforeNavigating()
|
||||||
|
{
|
||||||
|
_currentModalController?.Dispose();
|
||||||
|
_currentModalController = null;
|
||||||
|
|
||||||
|
if (_storyboard.IsValueCreated)
|
||||||
|
{
|
||||||
|
_storyboard.Value.Dispose();
|
||||||
|
_storyboard = null;
|
||||||
|
_storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ContinueOnAsync()
|
private async Task ContinueOnAsync()
|
||||||
{
|
{
|
||||||
Tuple<SendType, string, byte[], string> createSend = null;
|
Tuple<SendType, string, byte[], string> createSend = null;
|
||||||
@ -140,20 +184,24 @@ namespace Bit.iOS.ShareExtension
|
|||||||
CreateSend = createSend,
|
CreateSend = createSend,
|
||||||
CopyInsteadOfShareAfterSaving = true
|
CopyInsteadOfShareAfterSaving = true
|
||||||
};
|
};
|
||||||
var sendAddEditPage = new SendAddEditPage(appOptions)
|
var sendPage = new SendAddOnlyPage(appOptions)
|
||||||
{
|
{
|
||||||
OnClose = () => CompleteRequest(),
|
OnClose = () => CompleteRequest(),
|
||||||
AfterSubmit = () => CompleteRequest()
|
AfterSubmit = () => CompleteRequest()
|
||||||
};
|
};
|
||||||
|
|
||||||
var app = new App.App(appOptions);
|
SetupAppAndApplyResources(sendPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(sendAddEditPage);
|
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(sendAddEditPage);
|
NavigateToPage(sendPage);
|
||||||
var sendAddEditController = navigationPage.CreateViewController();
|
}
|
||||||
sendAddEditController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(sendAddEditController, true, null);
|
private void NavigateToPage(ContentPage page)
|
||||||
|
{
|
||||||
|
var navigationPage = new NavigationPage(page);
|
||||||
|
_currentModalController = navigationPage.CreateViewController();
|
||||||
|
_currentModalController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
_presentingOnNavigationPage = true;
|
||||||
|
PresentViewController(_currentModalController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string, byte[])> LoadDataBytesAsync()
|
private async Task<(string, byte[])> LoadDataBytesAsync()
|
||||||
@ -202,31 +250,6 @@ namespace Bit.iOS.ShareExtension
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitApp()
|
|
||||||
{
|
|
||||||
// Init Xamarin Forms
|
|
||||||
Forms.Init();
|
|
||||||
|
|
||||||
if (ServiceContainer.RegisteredServices.Count > 0)
|
|
||||||
{
|
|
||||||
ServiceContainer.Reset();
|
|
||||||
}
|
|
||||||
iOSCoreHelpers.RegisterLocalServices();
|
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent,
|
|
||||||
Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
|
||||||
iOSCoreHelpers.InitLogger();
|
|
||||||
iOSCoreHelpers.Bootstrap();
|
|
||||||
|
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
|
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
|
||||||
_nfcDelegate = new NFCReaderDelegate((success, message) =>
|
|
||||||
messagingService.Send("gotYubiKeyOTP", message));
|
|
||||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<bool> IsLocked()
|
private Task<bool> IsLocked()
|
||||||
{
|
{
|
||||||
return _vaultTimeoutService.Value.IsLockedAsync();
|
return _vaultTimeoutService.Value.IsLockedAsync();
|
||||||
@ -244,7 +267,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
if (await IsAuthed())
|
if (await IsAuthed())
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||||
if (_deviceActionService.Value.SystemMajorVersion() >= 12)
|
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
}
|
}
|
||||||
@ -252,83 +275,75 @@ namespace Bit.iOS.ShareExtension
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private App.App SetupAppAndApplyResources(ContentPage page)
|
||||||
|
{
|
||||||
|
if (_app is null)
|
||||||
|
{
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
}
|
||||||
|
ThemeManager.ApplyResourcesToPage(page);
|
||||||
|
return _app;
|
||||||
|
}
|
||||||
|
|
||||||
private void LaunchHomePage()
|
private void LaunchHomePage()
|
||||||
{
|
{
|
||||||
var homePage = new HomePage();
|
var homePage = new HomePage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(homePage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(homePage);
|
|
||||||
if (homePage.BindingContext is HomeViewModel vm)
|
if (homePage.BindingContext is HomeViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||||
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
|
vm.StartRegisterAction = () => DismissAndLaunch(() => LaunchRegisterFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
|
vm.StartEnvironmentAction = () => DismissAndLaunch(() => LaunchEnvironmentFlow());
|
||||||
vm.CloseAction = () => CompleteRequest();
|
vm.CloseAction = () => CompleteRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(homePage);
|
NavigateToPage(homePage);
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchEnvironmentFlow()
|
private void LaunchEnvironmentFlow()
|
||||||
{
|
{
|
||||||
var environmentPage = new EnvironmentPage();
|
var environmentPage = new EnvironmentPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(environmentPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(environmentPage);
|
ThemeManager.ApplyResourcesToPage(environmentPage);
|
||||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(environmentPage);
|
NavigateToPage(environmentPage);
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchRegisterFlow()
|
private void LaunchRegisterFlow()
|
||||||
{
|
{
|
||||||
var registerPage = new RegisterPage(null);
|
var registerPage = new RegisterPage(null);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(registerPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(registerPage);
|
|
||||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(registerPage);
|
||||||
var navigationPage = new NavigationPage(registerPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginFlow(string email = null)
|
private void LaunchLoginFlow(string email = null)
|
||||||
{
|
{
|
||||||
var loginPage = new LoginPage(email);
|
var loginPage = new LoginPage(email);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(loginPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
|
||||||
if (loginPage.BindingContext is LoginPageViewModel vm)
|
if (loginPage.BindingContext is LoginPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () =>
|
||||||
|
{
|
||||||
|
DismissLockAndContinue();
|
||||||
|
};
|
||||||
vm.CloseAction = () => CompleteRequest();
|
vm.CloseAction = () => CompleteRequest();
|
||||||
}
|
}
|
||||||
|
NavigateToPage(loginPage);
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
@ -336,22 +351,16 @@ namespace Bit.iOS.ShareExtension
|
|||||||
private void LaunchLoginSsoFlow()
|
private void LaunchLoginSsoFlow()
|
||||||
{
|
{
|
||||||
var loginPage = new LoginSsoPage();
|
var loginPage = new LoginSsoPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(loginPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
|
||||||
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(loginPage);
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
@ -359,65 +368,97 @@ namespace Bit.iOS.ShareExtension
|
|||||||
private void LaunchTwoFactorFlow(bool authingWithSso)
|
private void LaunchTwoFactorFlow(bool authingWithSso)
|
||||||
{
|
{
|
||||||
var twoFactorPage = new TwoFactorPage();
|
var twoFactorPage = new TwoFactorPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(twoFactorPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
|
||||||
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
if (authingWithSso)
|
if (authingWithSso)
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||||
}
|
}
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(twoFactorPage);
|
||||||
var navigationPage = new NavigationPage(twoFactorPage);
|
|
||||||
var twoFactorController = navigationPage.CreateViewController();
|
|
||||||
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(twoFactorController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchSetPasswordFlow()
|
private void LaunchSetPasswordFlow()
|
||||||
{
|
{
|
||||||
var setPasswordPage = new SetPasswordPage();
|
var setPasswordPage = new SetPasswordPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(setPasswordPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(setPasswordPage);
|
|
||||||
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(setPasswordPage);
|
||||||
var navigationPage = new NavigationPage(setPasswordPage);
|
|
||||||
var setPasswordController = navigationPage.CreateViewController();
|
|
||||||
setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(setPasswordController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchUpdateTempPasswordFlow()
|
private void LaunchUpdateTempPasswordFlow()
|
||||||
{
|
{
|
||||||
var updateTempPasswordPage = new UpdateTempPasswordPage();
|
var updateTempPasswordPage = new UpdateTempPasswordPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(updateTempPasswordPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(updateTempPasswordPage);
|
|
||||||
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
|
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.UpdateTempPasswordSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
vm.LogOutAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.LogOutAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
|
}
|
||||||
|
NavigateToPage(updateTempPasswordPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(updateTempPasswordPage);
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||||
var updateTempPasswordController = navigationPage.CreateViewController();
|
{
|
||||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
||||||
PresentViewController(updateTempPasswordController, true, null);
|
{
|
||||||
|
ExtNavigationController.PopViewController(false);
|
||||||
|
}
|
||||||
|
else if (ExtNavigationController?.ModalViewController != null)
|
||||||
|
{
|
||||||
|
ExtNavigationController.DismissModalViewController(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (navTarget)
|
||||||
|
{
|
||||||
|
case NavigationTarget.HomeLogin:
|
||||||
|
ExecuteLaunch(LaunchHomePage);
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Login:
|
||||||
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchLoginFlow(loginParams.Email));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchLoginFlow());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Lock:
|
||||||
|
NavigateToLockViewController();
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Home:
|
||||||
|
DismissLockAndContinue();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExecuteLaunch(Action launchAction)
|
||||||
|
{
|
||||||
|
if (_presentingOnNavigationPage)
|
||||||
|
{
|
||||||
|
DismissAndLaunch(launchAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launchAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
||||||
|
public Task UpdateThemeAsync() => Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.ShareExtension
|
namespace Bit.iOS.ShareExtension
|
||||||
{
|
{
|
||||||
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController
|
public partial class LockPasswordViewController : Core.Controllers.BaseLockPasswordViewController
|
||||||
{
|
{
|
||||||
|
AccountSwitchingOverlayView _accountSwitchingOverlayView;
|
||||||
|
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
|
||||||
|
|
||||||
|
public LockPasswordViewController()
|
||||||
|
{
|
||||||
|
BiometricIntegrityKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegrityKey;
|
||||||
|
DismissModalAction = Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
public LockPasswordViewController(IntPtr handle)
|
public LockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@ -17,24 +28,80 @@ namespace Bit.iOS.ShareExtension
|
|||||||
public override UINavigationItem BaseNavItem => _navItem;
|
public override UINavigationItem BaseNavItem => _navItem;
|
||||||
public override UIBarButtonItem BaseCancelButton => _cancelButton;
|
public override UIBarButtonItem BaseCancelButton => _cancelButton;
|
||||||
public override UIBarButtonItem BaseSubmitButton => _submitButton;
|
public override UIBarButtonItem BaseSubmitButton => _submitButton;
|
||||||
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
public override Action Success => () =>
|
||||||
public override Action Cancel => () => LoadingController.CompleteRequest();
|
{
|
||||||
|
LoadingController?.Navigate(Bit.Core.Enums.NavigationTarget.Home);
|
||||||
|
LoadingController = null;
|
||||||
|
};
|
||||||
|
public override Action Cancel => () =>
|
||||||
|
{
|
||||||
|
LoadingController?.CompleteRequest();
|
||||||
|
LoadingController = null;
|
||||||
|
};
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
public override UITableView TableView => _mainTableView;
|
||||||
|
|
||||||
|
public override async void ViewDidLoad()
|
||||||
{
|
{
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
_cancelButton.TintColor = ThemeHelpers.NavBarTextColor;
|
_cancelButton.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
_submitButton.TintColor = ThemeHelpers.NavBarTextColor;
|
_submitButton.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
|
|
||||||
|
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
|
||||||
|
_accountSwitchingButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
|
||||||
|
|
||||||
|
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(_overlayView);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateNavigationBarTheme()
|
||||||
|
{
|
||||||
|
UpdateNavigationBarTheme(_navBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void AccountSwitchingButton_Activated(UIBarButtonItem sender)
|
||||||
|
{
|
||||||
|
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, _overlayView);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
var task = CheckPasswordAsync();
|
CheckPasswordAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
Cancel();
|
Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (TableView != null)
|
||||||
|
{
|
||||||
|
TableView.Source?.Dispose();
|
||||||
|
}
|
||||||
|
if (_accountSwitchingButton?.Image != null)
|
||||||
|
{
|
||||||
|
var img = _accountSwitchingButton.Image;
|
||||||
|
_accountSwitchingButton.Image = null;
|
||||||
|
img.Dispose();
|
||||||
|
}
|
||||||
|
if (_accountSwitchingOverlayView != null && _overlayView?.Subviews != null)
|
||||||
|
{
|
||||||
|
foreach (var subView in _overlayView.Subviews)
|
||||||
|
{
|
||||||
|
subView.RemoveFromSuperview();
|
||||||
|
subView.Dispose();
|
||||||
|
}
|
||||||
|
_accountSwitchingOverlayView = null;
|
||||||
|
_overlayView.RemoveFromSuperview();
|
||||||
|
}
|
||||||
|
_accountSwitchingOverlayHelper = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,18 +12,30 @@ namespace Bit.iOS.ShareExtension
|
|||||||
[Register ("LockPasswordViewController")]
|
[Register ("LockPasswordViewController")]
|
||||||
partial class LockPasswordViewController
|
partial class LockPasswordViewController
|
||||||
{
|
{
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UIBarButtonItem _accountSwitchingButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UIBarButtonItem _cancelButton { get; set; }
|
UIKit.UIBarButtonItem _cancelButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UITableView _mainTableView { get; set; }
|
UIKit.UITableView _mainTableView { get; set; }
|
||||||
|
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UINavigationBar _navBar { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UINavigationItem _navItem { get; set; }
|
UIKit.UINavigationItem _navItem { get; set; }
|
||||||
|
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UIView _overlayView { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UIBarButtonItem _submitButton { get; set; }
|
UIKit.UIBarButtonItem _submitButton { get; set; }
|
||||||
|
|
||||||
|
[Action ("AccountSwitchingButton_Activated:")]
|
||||||
|
partial void AccountSwitchingButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
|
|
||||||
[Action ("CancelButton_Activated:")]
|
[Action ("CancelButton_Activated:")]
|
||||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
|
|
||||||
@ -32,6 +44,11 @@ namespace Bit.iOS.ShareExtension
|
|||||||
|
|
||||||
void ReleaseDesignerOutlets ()
|
void ReleaseDesignerOutlets ()
|
||||||
{
|
{
|
||||||
|
if (_accountSwitchingButton != null) {
|
||||||
|
_accountSwitchingButton.Dispose ();
|
||||||
|
_accountSwitchingButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_cancelButton != null) {
|
if (_cancelButton != null) {
|
||||||
_cancelButton.Dispose ();
|
_cancelButton.Dispose ();
|
||||||
_cancelButton = null;
|
_cancelButton = null;
|
||||||
@ -47,10 +64,20 @@ namespace Bit.iOS.ShareExtension
|
|||||||
_navItem = null;
|
_navItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_overlayView != null) {
|
||||||
|
_overlayView.Dispose ();
|
||||||
|
_overlayView = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_submitButton != null) {
|
if (_submitButton != null) {
|
||||||
_submitButton.Dispose ();
|
_submitButton.Dispose ();
|
||||||
_submitButton = null;
|
_submitButton = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_navBar != null) {
|
||||||
|
_navBar.Dispose ();
|
||||||
|
_navBar = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="2vH-Do-uhk">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="2vH-Do-uhk">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@ -11,10 +13,6 @@
|
|||||||
<scene sceneID="kFr-IN-5GS">
|
<scene sceneID="kFr-IN-5GS">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="bHU-LX-EpF" customClass="LoadingViewController" sceneMemberID="viewController">
|
<viewController id="bHU-LX-EpF" customClass="LoadingViewController" sceneMemberID="viewController">
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="8LE-gl-yDT"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="MuK-nA-9iu"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="z2O-Vp-jY9">
|
<view key="view" contentMode="scaleToFill" id="z2O-Vp-jY9">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
@ -23,25 +21,25 @@
|
|||||||
<rect key="frame" x="66" y="352" width="282" height="44"/>
|
<rect key="frame" x="66" y="352" width="282" height="44"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="jNx-Vd-K6U"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerX" secondItem="z2O-Vp-jY9" secondAttribute="centerX" id="6DT-HB-vS5"/>
|
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerX" secondItem="jNx-Vd-K6U" secondAttribute="centerX" id="6DT-HB-vS5"/>
|
||||||
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerY" secondItem="z2O-Vp-jY9" secondAttribute="centerY" constant="-30" id="o9N-Tv-Iwq"/>
|
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerY" secondItem="z2O-Vp-jY9" secondAttribute="centerY" constant="-30" id="o9N-Tv-Iwq"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" id="74l-Va-Vqa"/>
|
<navigationItem key="navigationItem" id="74l-Va-Vqa"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="Logo" destination="Zdy-yw-n0p" id="1Qk-EK-0BO"/>
|
<outlet property="Logo" destination="Zdy-yw-n0p" id="1Qk-EK-0BO"/>
|
||||||
<segue destination="rh6-Mf-4Ja" kind="presentation" identifier="lockPasswordSegue" id="ZUl-jv-5se"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yJx-cc-wzs" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="yJx-cc-wzs" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-374" y="560"/>
|
<point key="canvasLocation" x="-374" y="560"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Navigation Controller-->
|
<!--Extension Navigation Controller-->
|
||||||
<scene sceneID="Wgx-vz-XqL">
|
<scene sceneID="Wgx-vz-XqL">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="2vH-Do-uhk" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="2vH-Do-uhk" customClass="ExtensionNavigationController" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" hidden="YES" contentMode="scaleToFill" translucent="NO" id="JoO-jQ-16M">
|
<navigationBar key="navigationBar" hidden="YES" contentMode="scaleToFill" translucent="NO" id="JoO-jQ-16M">
|
||||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
@ -54,60 +52,89 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-1097" y="564"/>
|
<point key="canvasLocation" x="-1097" y="564"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Navigation Controller-->
|
<!--Lock Password View Controller-->
|
||||||
<scene sceneID="Tzp-2o-9k7">
|
<scene sceneID="vQB-cT-8IC">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="rh6-Mf-4Ja" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="lockVC" id="Vi7-LV-nWW" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="UDq-kw-Ue7">
|
<view key="view" contentMode="scaleToFill" id="Vfd-7B-19G">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
|
||||||
</navigationBar>
|
|
||||||
<connections>
|
|
||||||
<segue destination="85y-W9-d8q" kind="relationship" relationship="rootViewController" id="TeA-GE-A22"/>
|
|
||||||
</connections>
|
|
||||||
</navigationController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="BVV-5B-aim" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="-375" y="1262"/>
|
|
||||||
</scene>
|
|
||||||
<!--Verify Master Password-->
|
|
||||||
<scene sceneID="OEb-ak-BVc">
|
|
||||||
<objects>
|
|
||||||
<tableViewController id="85y-W9-d8q" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="9on-wf-zdb">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<subviews>
|
||||||
<connections>
|
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" estimatedSectionHeaderHeight="-1" sectionFooterHeight="18" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="M1A-84-x5l">
|
||||||
<outlet property="dataSource" destination="85y-W9-d8q" id="3il-RO-S3K"/>
|
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||||
<outlet property="delegate" destination="85y-W9-d8q" id="bLb-h4-pr3"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</connections>
|
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Verify Master Password" id="qL3-iV-6Ld">
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ijE-Pa-OBq" userLabel="OverlayView">
|
||||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="d8j-HZ-erD">
|
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</view>
|
||||||
|
<navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fav-Fz-6ZK">
|
||||||
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
|
<items>
|
||||||
|
<navigationItem title="Verify Master Password" id="aka-In-IYk">
|
||||||
|
<leftBarButtonItems>
|
||||||
|
<barButtonItem title="Cancel" id="LrG-Qx-w4Q">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="CancelButton_Activated:" destination="85y-W9-d8q" id="p54-B0-Vyf"/>
|
<action selector="CancelButton_Activated:" destination="Vi7-LV-nWW" id="qyZ-i9-Dwz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
<barButtonItem key="rightBarButtonItem" title="Submit" id="8a7-Vz-SJA">
|
<barButtonItem title="Item" image="person.2" catalog="system" style="plain" id="nlD-Xn-HtM" userLabel="Account Switching Button">
|
||||||
|
<color key="tintColor" systemColor="systemBackgroundColor"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="SubmitButton_Activated:" destination="85y-W9-d8q" id="P8A-7O-lpY"/>
|
<action selector="AccountSwitchingButton_Activated:" destination="Vi7-LV-nWW" id="G3U-rv-UOl"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</leftBarButtonItems>
|
||||||
|
<barButtonItem key="rightBarButtonItem" title="Submit" id="oQD-QK-YPB">
|
||||||
|
<connections>
|
||||||
|
<action selector="SubmitButton_Activated:" destination="Vi7-LV-nWW" id="DgO-TS-MPf"/>
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
|
</items>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="barPosition">
|
||||||
|
<integer key="value" value="3"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</navigationBar>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="SSW-s3-JwL"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="M1A-84-x5l" firstAttribute="leading" secondItem="SSW-s3-JwL" secondAttribute="leading" id="3Es-aL-5Og"/>
|
||||||
|
<constraint firstItem="ijE-Pa-OBq" firstAttribute="leading" secondItem="SSW-s3-JwL" secondAttribute="leading" id="6Lj-CR-OFz"/>
|
||||||
|
<constraint firstItem="fav-Fz-6ZK" firstAttribute="leading" secondItem="SSW-s3-JwL" secondAttribute="leading" id="BEJ-gh-NAq"/>
|
||||||
|
<constraint firstItem="fav-Fz-6ZK" firstAttribute="top" secondItem="SSW-s3-JwL" secondAttribute="top" id="CLE-2p-LI3"/>
|
||||||
|
<constraint firstItem="SSW-s3-JwL" firstAttribute="trailing" secondItem="M1A-84-x5l" secondAttribute="trailing" id="GaL-B0-2Lg"/>
|
||||||
|
<constraint firstItem="SSW-s3-JwL" firstAttribute="bottom" secondItem="M1A-84-x5l" secondAttribute="bottom" id="LG1-vj-VhW"/>
|
||||||
|
<constraint firstItem="SSW-s3-JwL" firstAttribute="trailing" secondItem="ijE-Pa-OBq" secondAttribute="trailing" id="Q3J-Wa-mnY"/>
|
||||||
|
<constraint firstItem="ijE-Pa-OBq" firstAttribute="top" secondItem="fav-Fz-6ZK" secondAttribute="bottom" id="h8T-rn-ZPU"/>
|
||||||
|
<constraint firstItem="SSW-s3-JwL" firstAttribute="trailing" secondItem="fav-Fz-6ZK" secondAttribute="trailing" id="tux-AN-Z92"/>
|
||||||
|
<constraint firstItem="SSW-s3-JwL" firstAttribute="bottom" secondItem="ijE-Pa-OBq" secondAttribute="bottom" id="zLh-RX-eSc"/>
|
||||||
|
<constraint firstItem="M1A-84-x5l" firstAttribute="top" secondItem="fav-Fz-6ZK" secondAttribute="bottom" id="zgM-he-DYl"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="_cancelButton" destination="d8j-HZ-erD" id="wlI-el-Snh"/>
|
<outlet property="_accountSwitchingButton" destination="nlD-Xn-HtM" id="SSG-zv-bAc"/>
|
||||||
<outlet property="_mainTableView" destination="9on-wf-zdb" id="ltj-yY-5ue"/>
|
<outlet property="_cancelButton" destination="LrG-Qx-w4Q" id="aag-ZZ-Ifs"/>
|
||||||
<outlet property="_navItem" destination="qL3-iV-6Ld" id="Grb-Ta-NCF"/>
|
<outlet property="_mainTableView" destination="M1A-84-x5l" id="pA4-ao-Fhu"/>
|
||||||
<outlet property="_submitButton" destination="8a7-Vz-SJA" id="LS8-6Y-Wkp"/>
|
<outlet property="_navBar" destination="fav-Fz-6ZK" id="Q9p-Dw-ipx"/>
|
||||||
|
<outlet property="_navItem" destination="aka-In-IYk" id="www-Lt-x1g"/>
|
||||||
|
<outlet property="_overlayView" destination="ijE-Pa-OBq" id="n9e-Lg-4WO"/>
|
||||||
|
<outlet property="_submitButton" destination="oQD-QK-YPB" id="SEp-KK-YeP"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="5by-Sa-d9m" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Czu-9n-yKC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="335" y="1260"/>
|
<point key="canvasLocation" x="403" y="560"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="logo.png" width="282" height="44"/>
|
<image name="logo.png" width="282" height="44"/>
|
||||||
|
<image name="person.2" catalog="system" width="128" height="81"/>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
<MtouchLink>None</MtouchLink>
|
<MtouchLink>None</MtouchLink>
|
||||||
<MtouchArch>x86_64</MtouchArch>
|
<MtouchArch>x86_64</MtouchArch>
|
||||||
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
||||||
<DeviceSpecificBuild>false</DeviceSpecificBuild>
|
|
||||||
<MtouchVerbosity></MtouchVerbosity>
|
<MtouchVerbosity></MtouchVerbosity>
|
||||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||||
<AssemblyName>BitwardeniOSShareExtension</AssemblyName>
|
<AssemblyName>BitwardeniOSShareExtension</AssemblyName>
|
||||||
@ -193,6 +192,10 @@
|
|||||||
<Compile Include="LockPasswordViewController.designer.cs">
|
<Compile Include="LockPasswordViewController.designer.cs">
|
||||||
<DependentUpon>LockPasswordViewController.cs</DependentUpon>
|
<DependentUpon>LockPasswordViewController.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="ExtensionNavigationController.cs" />
|
||||||
|
<Compile Include="ExtensionNavigationController.designer.cs">
|
||||||
|
<DependentUpon>ExtensionNavigationController.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\App\App.csproj">
|
<ProjectReference Include="..\App\App.csproj">
|
||||||
|
Loading…
Reference in New Issue
Block a user