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.Pages;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.App.Controls;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
@ -69,7 +70,8 @@ namespace Bit.Droid
|
||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||
ServiceContainer.Resolve<ILogger>("logger"));
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
}
|
||||
#if !FDROID
|
||||
@ -160,6 +162,7 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
|
@ -129,12 +129,10 @@
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
<Folder Include="Utilities\AccountManagement\" />
|
||||
<Folder Include="Controls\DateTime\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -162,12 +160,6 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Styles\Base.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
||||
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
||||
@ -422,5 +414,6 @@
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||
<None Remove="Utilities\AccountManagement\" />
|
||||
<None Remove="Controls\DateTime\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -13,7 +13,8 @@ namespace Bit.App.Controls
|
||||
public AccountViewCellViewModel(AccountView accountView)
|
||||
{
|
||||
AccountView = accountView;
|
||||
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
|
||||
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
||||
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
|
||||
}
|
||||
|
||||
public AccountView AccountView
|
||||
|
@ -50,7 +50,7 @@ namespace Bit.App.Controls
|
||||
|
||||
private Stream Draw()
|
||||
{
|
||||
string chars = null;
|
||||
string chars;
|
||||
string upperData = null;
|
||||
|
||||
if (string.IsNullOrEmpty(_data))
|
||||
@ -71,30 +71,39 @@ namespace Bit.App.Controls
|
||||
var textColor = Color.White;
|
||||
var size = 50;
|
||||
|
||||
var bitmap = new SKBitmap(
|
||||
size * 2,
|
||||
using (var bitmap = new SKBitmap(size * 2,
|
||||
size * 2,
|
||||
SKImageInfo.PlatformColorType,
|
||||
SKAlphaType.Premul);
|
||||
var canvas = new SKCanvas(bitmap);
|
||||
SKAlphaType.Premul))
|
||||
{
|
||||
using (var canvas = new SKCanvas(bitmap))
|
||||
{
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
|
||||
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
|
||||
using (var paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
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);
|
||||
|
||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||
var textSize = midX / 1.3f;
|
||||
var textPaint = new SKPaint
|
||||
using (var textPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
@ -102,12 +111,22 @@ namespace Bit.App.Controls
|
||||
TextSize = textSize,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
Typeface = typeface
|
||||
};
|
||||
})
|
||||
{
|
||||
var rect = new SKRect();
|
||||
textPaint.MeasureText(chars, ref rect);
|
||||
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)
|
||||
|
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
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
@ -303,14 +303,14 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:ExtendedDatePicker
|
||||
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
|
||||
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
|
||||
Format="d"
|
||||
IsEnabled="{Binding SendEnabled}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n DeletionDate}"
|
||||
Grid.Column="0" />
|
||||
<controls:ExtendedTimePicker
|
||||
NullableTime="{Binding DeletionTime, Mode=TwoWay}"
|
||||
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
||||
Format="t"
|
||||
IsEnabled="{Binding SendEnabled}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
@ -343,7 +343,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:ExtendedDatePicker
|
||||
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
|
||||
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
|
||||
PlaceHolder="mm/dd/yyyy"
|
||||
Format="d"
|
||||
IsEnabled="{Binding SendEnabled}"
|
||||
@ -351,7 +351,7 @@
|
||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||
Grid.Column="0" />
|
||||
<controls:ExtendedTimePicker
|
||||
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
|
||||
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
||||
PlaceHolder="--:-- --"
|
||||
Format="t"
|
||||
IsEnabled="{Binding SendEnabled}"
|
||||
|
@ -23,7 +23,6 @@ namespace Bit.App.Pages
|
||||
private AppOptions _appOptions;
|
||||
private SendAddEditPageViewModel _vm;
|
||||
|
||||
public Action OnClose { get; set; }
|
||||
public Action AfterSubmit { get; set; }
|
||||
|
||||
public SendAddEditPage(
|
||||
@ -135,16 +134,9 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
private async Task CloseAsync()
|
||||
{
|
||||
if (OnClose is null)
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnClose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
@ -23,7 +24,7 @@ namespace Bit.App.Pages
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ISendService _sendService;
|
||||
private readonly ILogger _logger;
|
||||
private bool _sendEnabled;
|
||||
private bool _sendEnabled = true;
|
||||
private bool _canAccessPremium;
|
||||
private bool _emailVerified;
|
||||
private SendView _send;
|
||||
@ -33,11 +34,7 @@ namespace Bit.App.Pages
|
||||
private int _deletionDateTypeSelectedIndex;
|
||||
private int _expirationDateTypeSelectedIndex;
|
||||
private DateTime _simpleDeletionDateTime;
|
||||
private DateTime _deletionDate;
|
||||
private TimeSpan _deletionTime;
|
||||
private DateTime? _simpleExpirationDateTime;
|
||||
private DateTime? _expirationDate;
|
||||
private TimeSpan? _expirationTime;
|
||||
private bool _isOverridingPickers;
|
||||
private int? _maxAccessCount;
|
||||
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.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 ToggleOptionsCommand { 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
|
||||
{
|
||||
get => _showOptions;
|
||||
set => SetProperty(ref _showOptions, value,
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(OptionsAccessilibityText)
|
||||
nameof(OptionsAccessilibityText),
|
||||
nameof(OptionsShowHideIcon)
|
||||
});
|
||||
}
|
||||
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
|
||||
{
|
||||
get => _maxAccessCount;
|
||||
@ -205,7 +198,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
public string FileName
|
||||
{
|
||||
get => _fileName;
|
||||
get => _fileName ?? AppResources.NoFileChosen;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _fileName, value))
|
||||
@ -240,10 +233,13 @@ namespace Bit.App.Pages
|
||||
public bool IsFile => Send?.Type == SendType.File;
|
||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||
public DateTimeViewModel DeletionDateTimeViewModel { get; }
|
||||
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
@ -268,10 +264,8 @@ namespace Bit.App.Pages
|
||||
return false;
|
||||
}
|
||||
Send = await send.DecryptAsync();
|
||||
DeletionDate = Send.DeletionDate.ToLocalTime();
|
||||
DeletionTime = DeletionDate.TimeOfDay;
|
||||
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
|
||||
ExpirationTime = ExpirationDate?.TimeOfDay;
|
||||
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
|
||||
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -280,8 +274,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
Type = Type.GetValueOrDefault(defaultType),
|
||||
};
|
||||
_deletionDate = DateTimeNow().AddDays(7);
|
||||
_deletionTime = DeletionDate.TimeOfDay;
|
||||
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
|
||||
DeletionDateTypeSelectedIndex = 4;
|
||||
ExpirationDateTypeSelectedIndex = 0;
|
||||
}
|
||||
@ -305,23 +298,22 @@ namespace Bit.App.Pages
|
||||
public void ClearExpirationDate()
|
||||
{
|
||||
_isOverridingPickers = true;
|
||||
ExpirationDate = null;
|
||||
ExpirationTime = null;
|
||||
ExpirationDateTimeViewModel.DateTime = null;
|
||||
_isOverridingPickers = false;
|
||||
}
|
||||
|
||||
private void UpdateSendData()
|
||||
{
|
||||
// filename
|
||||
if (Send.File != null && FileName != null)
|
||||
if (Send.File != null && _fileName != null)
|
||||
{
|
||||
Send.File.FileName = FileName;
|
||||
Send.File.FileName = _fileName;
|
||||
}
|
||||
|
||||
// deletion date
|
||||
if (ShowDeletionCustomPickers)
|
||||
{
|
||||
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
|
||||
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -329,9 +321,9 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
@ -484,7 +476,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
|
||||
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
|
||||
{
|
||||
sendPage.OnClose();
|
||||
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()
|
||||
{
|
||||
Send.MaxAccessCount = _maxAccessCount;
|
||||
@ -666,5 +640,10 @@ namespace Bit.App.Pages
|
||||
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 IPlatformUtilsService _platformUtilsService;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
Func<AppOptions> _getOptionsFunc;
|
||||
private IAccountsManagerHost _accountsManagerHost;
|
||||
@ -28,7 +29,8 @@ namespace Bit.App.Utilities.AccountManagement
|
||||
IStorageService secureStorageService,
|
||||
IStateService stateService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IAuthService authService)
|
||||
IAuthService authService,
|
||||
ILogger logger)
|
||||
{
|
||||
_broadcasterService = broadcasterService;
|
||||
_vaultTimeoutService = vaultTimeoutService;
|
||||
@ -36,6 +38,7 @@ namespace Bit.App.Utilities.AccountManagement
|
||||
_stateService = stateService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_authService = authService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||
@ -108,24 +111,26 @@ namespace Bit.App.Utilities.AccountManagement
|
||||
}
|
||||
|
||||
private async void OnMessage(Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (message.Command)
|
||||
{
|
||||
case AccountsManagerMessageCommands.LOCKED:
|
||||
Locked(message.Data as Tuple<string, bool>);
|
||||
await Device.InvokeOnMainThreadAsync(() => LockedAsync(message.Data as Tuple<string, bool>));
|
||||
break;
|
||||
case AccountsManagerMessageCommands.LOCK_VAULT:
|
||||
await _vaultTimeoutService.LockAsync(true);
|
||||
break;
|
||||
case AccountsManagerMessageCommands.LOGOUT:
|
||||
LogOut(message.Data as Tuple<string, bool, bool>);
|
||||
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(message.Data as Tuple<string, bool, bool>));
|
||||
break;
|
||||
case AccountsManagerMessageCommands.LOGGED_OUT:
|
||||
// Clean up old migrated key if they ever log out.
|
||||
await _secureStorageService.RemoveAsync("oldKey");
|
||||
break;
|
||||
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
||||
AddAccount();
|
||||
await AddAccountAsync();
|
||||
break;
|
||||
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
||||
await _accountsManagerHost.UpdateThemeAsync();
|
||||
@ -135,16 +140,17 @@ namespace Bit.App.Utilities.AccountManagement
|
||||
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 userInitiated = extras?.Item2 ?? false;
|
||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
||||
}
|
||||
|
||||
private async Task LockedAsync(string userId, bool userInitiated)
|
||||
{
|
||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||
{
|
||||
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||
@ -163,28 +169,24 @@ namespace Bit.App.Utilities.AccountManagement
|
||||
|
||||
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;
|
||||
_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 userInitiated = extras?.Item2 ?? true;
|
||||
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 NavigateOnAccountChangeAsync();
|
||||
_authService.LogOut(() =>
|
||||
|
@ -1,20 +1,20 @@
|
||||
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 Bit.App.Utilities;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Abstractions;
|
||||
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.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
|
||||
{
|
||||
@ -39,6 +39,10 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
protected bool autofillExtension = false;
|
||||
|
||||
public BaseLockPasswordViewController()
|
||||
{
|
||||
}
|
||||
|
||||
public BaseLockPasswordViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{ }
|
||||
@ -168,12 +172,11 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||
}
|
||||
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 70;
|
||||
TableView.Source = new TableSource(this);
|
||||
TableView.AllowsSelection = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -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
|
||||
{
|
||||
private readonly BaseLockPasswordViewController _controller;
|
||||
private readonly WeakReference<BaseLockPasswordViewController> _controller;
|
||||
|
||||
public TableSource(BaseLockPasswordViewController controller)
|
||||
{
|
||||
_controller = controller;
|
||||
_controller = new WeakReference<BaseLockPasswordViewController>(controller);
|
||||
}
|
||||
|
||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
if (!_controller.TryGetTarget(out var controller))
|
||||
{
|
||||
return new ExtendedUITableViewCell();
|
||||
}
|
||||
|
||||
if (indexPath.Section == 0)
|
||||
{
|
||||
if (indexPath.Row == 0)
|
||||
{
|
||||
if (_controller._biometricUnlockOnly)
|
||||
if (controller._biometricUnlockOnly)
|
||||
{
|
||||
return _controller.BiometricCell;
|
||||
return controller.BiometricCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _controller.MasterPasswordCell;
|
||||
return controller.MasterPasswordCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -431,7 +449,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
if (indexPath.Row == 0)
|
||||
{
|
||||
if (_controller._passwordReprompt)
|
||||
if (controller._passwordReprompt)
|
||||
{
|
||||
var cell = new ExtendedUITableViewCell();
|
||||
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||
@ -441,9 +459,9 @@ namespace Bit.iOS.Core.Controllers
|
||||
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
||||
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)
|
||||
{
|
||||
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
||||
_controller._passwordReprompt
|
||||
if (!_controller.TryGetTarget(out var controller))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
||||
controller._passwordReprompt
|
||||
? 2
|
||||
: 1;
|
||||
}
|
||||
@ -484,13 +507,18 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
if (!_controller.TryGetTarget(out var controller))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tableView.DeselectRow(indexPath, true);
|
||||
tableView.EndEditing(true);
|
||||
if (indexPath.Row == 0 &&
|
||||
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||
((controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||
indexPath.Section == 1))
|
||||
{
|
||||
var task = _controller.PromptBiometricAsync();
|
||||
var task = controller.PromptBiometricAsync();
|
||||
return;
|
||||
}
|
||||
var cell = tableView.CellAt(indexPath);
|
||||
|
@ -8,6 +8,10 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public Action DismissModalAction { get; set; }
|
||||
|
||||
public ExtendedUIViewController()
|
||||
{
|
||||
}
|
||||
|
||||
public ExtendedUIViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{
|
||||
@ -28,16 +32,28 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||
}
|
||||
if (NavigationController?.NavigationBar != null)
|
||||
UpdateNavigationBarTheme();
|
||||
}
|
||||
|
||||
protected virtual void UpdateNavigationBarTheme()
|
||||
{
|
||||
NavigationController.NavigationBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
||||
NavigationController.NavigationBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
||||
NavigationController.NavigationBar.TintColor = ThemeHelpers.NavBarTextColor;
|
||||
NavigationController.NavigationBar.TitleTextAttributes = new UIStringAttributes
|
||||
UpdateNavigationBarTheme(NavigationController?.NavigationBar);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -13,9 +13,9 @@ namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
|
||||
|
||||
IStateService _stateService;
|
||||
IMessagingService _messagingService;
|
||||
ILogger _logger;
|
||||
readonly IStateService _stateService;
|
||||
readonly IMessagingService _messagingService;
|
||||
readonly ILogger _logger;
|
||||
|
||||
public AccountSwitchingOverlayHelper()
|
||||
{
|
||||
@ -34,9 +34,11 @@ namespace Bit.iOS.Core.Utilities
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
@ -15,6 +16,7 @@ using Bit.iOS.Core.Services;
|
||||
using CoreNFC;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
|
||||
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 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()
|
||||
{
|
||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||
@ -89,6 +127,7 @@ namespace Bit.iOS.Core.Utilities
|
||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||
}
|
||||
|
||||
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
||||
@ -181,7 +220,8 @@ namespace Bit.iOS.Core.Utilities
|
||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||
ServiceContainer.Resolve<ILogger>("logger"));
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
|
||||
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.Pages;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core;
|
||||
using Bit.iOS.Core.Controllers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
@ -24,16 +24,33 @@ using Xamarin.Forms;
|
||||
|
||||
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 NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
private IAccountsManager _accountsManager;
|
||||
|
||||
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||
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)
|
||||
: base(handle)
|
||||
@ -41,39 +58,38 @@ namespace Bit.iOS.ShareExtension
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
InitApp();
|
||||
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||
_nfcSession, out _nfcDelegate, out _accountsManager);
|
||||
|
||||
base.ViewDidLoad();
|
||||
|
||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
||||
_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)
|
||||
{
|
||||
var processed = false;
|
||||
foreach (var itemProvider in item.Attachments)
|
||||
{
|
||||
if (itemProvider.HasItemConformingTo(UTType.PlainText))
|
||||
{
|
||||
_context.ProviderType = UTType.PlainText;
|
||||
return UTType.PlainText;
|
||||
}
|
||||
|
||||
processed = true;
|
||||
break;
|
||||
}
|
||||
else if (itemProvider.HasItemConformingTo(UTType.Data))
|
||||
if (itemProvider.HasItemConformingTo(UTType.Data))
|
||||
{
|
||||
_context.ProviderType = UTType.Data;
|
||||
|
||||
processed = true;
|
||||
break;
|
||||
return UTType.Data;
|
||||
}
|
||||
}
|
||||
if (processed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async void ViewDidAppear(bool animated)
|
||||
@ -84,12 +100,12 @@ namespace Bit.iOS.ShareExtension
|
||||
{
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
LaunchHomePage();
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
return;
|
||||
}
|
||||
else if (await IsLocked())
|
||||
{
|
||||
PerformSegue("lockPasswordSegue", this);
|
||||
NavigateToLockViewController();
|
||||
}
|
||||
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
|
||||
&&
|
||||
navController.TopViewController is LockPasswordViewController passwordViewController)
|
||||
var viewController = _storyboard.Value.InstantiateViewController("lockVC") as LockPasswordViewController;
|
||||
viewController.LoadingController = this;
|
||||
viewController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
|
||||
if (_presentingOnNavigationPage)
|
||||
{
|
||||
passwordViewController.LoadingController = this;
|
||||
segue.DestinationViewController.PresentationController.Delegate =
|
||||
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
||||
_presentingOnNavigationPage = false;
|
||||
DismissViewController(true, () => ExtNavigationController.PushViewController(viewController, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtNavigationController.PushViewController(viewController, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DismissLockAndContinue()
|
||||
{
|
||||
Debug.WriteLine("BW Log, Dismissing lock controller.");
|
||||
|
||||
ClearBeforeNavigating();
|
||||
|
||||
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()
|
||||
{
|
||||
Tuple<SendType, string, byte[], string> createSend = null;
|
||||
@ -140,20 +184,24 @@ namespace Bit.iOS.ShareExtension
|
||||
CreateSend = createSend,
|
||||
CopyInsteadOfShareAfterSaving = true
|
||||
};
|
||||
var sendAddEditPage = new SendAddEditPage(appOptions)
|
||||
var sendPage = new SendAddOnlyPage(appOptions)
|
||||
{
|
||||
OnClose = () => CompleteRequest(),
|
||||
AfterSubmit = () => CompleteRequest()
|
||||
};
|
||||
|
||||
var app = new App.App(appOptions);
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(sendAddEditPage);
|
||||
SetupAppAndApplyResources(sendPage);
|
||||
|
||||
var navigationPage = new NavigationPage(sendAddEditPage);
|
||||
var sendAddEditController = navigationPage.CreateViewController();
|
||||
sendAddEditController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(sendAddEditController, true, null);
|
||||
NavigateToPage(sendPage);
|
||||
}
|
||||
|
||||
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()
|
||||
@ -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()
|
||||
{
|
||||
return _vaultTimeoutService.Value.IsLockedAsync();
|
||||
@ -244,7 +267,7 @@ namespace Bit.iOS.ShareExtension
|
||||
if (await IsAuthed())
|
||||
{
|
||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||
if (_deviceActionService.Value.SystemMajorVersion() >= 12)
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||
{
|
||||
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()
|
||||
{
|
||||
var homePage = new HomePage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(homePage);
|
||||
SetupAppAndApplyResources(homePage);
|
||||
if (homePage.BindingContext is HomeViewModel vm)
|
||||
{
|
||||
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
|
||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
|
||||
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||
vm.StartRegisterAction = () => DismissAndLaunch(() => LaunchRegisterFlow());
|
||||
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||
vm.StartEnvironmentAction = () => DismissAndLaunch(() => LaunchEnvironmentFlow());
|
||||
vm.CloseAction = () => CompleteRequest();
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(homePage);
|
||||
var loginController = navigationPage.CreateViewController();
|
||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginController, true, null);
|
||||
|
||||
NavigateToPage(homePage);
|
||||
LogoutIfAuthed();
|
||||
}
|
||||
|
||||
private void LaunchEnvironmentFlow()
|
||||
{
|
||||
var environmentPage = new EnvironmentPage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
SetupAppAndApplyResources(environmentPage);
|
||||
ThemeManager.ApplyResourcesToPage(environmentPage);
|
||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||
{
|
||||
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(environmentPage);
|
||||
var loginController = navigationPage.CreateViewController();
|
||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginController, true, null);
|
||||
NavigateToPage(environmentPage);
|
||||
}
|
||||
|
||||
private void LaunchRegisterFlow()
|
||||
{
|
||||
var registerPage = new RegisterPage(null);
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(registerPage);
|
||||
SetupAppAndApplyResources(registerPage);
|
||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||
{
|
||||
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(registerPage);
|
||||
var loginController = navigationPage.CreateViewController();
|
||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginController, true, null);
|
||||
NavigateToPage(registerPage);
|
||||
}
|
||||
|
||||
private void LaunchLoginFlow(string email = null)
|
||||
{
|
||||
var loginPage = new LoginPage(email);
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||
SetupAppAndApplyResources(loginPage);
|
||||
if (loginPage.BindingContext is LoginPageViewModel vm)
|
||||
{
|
||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
vm.LogInSuccessAction = () =>
|
||||
{
|
||||
DismissLockAndContinue();
|
||||
};
|
||||
vm.CloseAction = () => CompleteRequest();
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(loginPage);
|
||||
var loginController = navigationPage.CreateViewController();
|
||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginController, true, null);
|
||||
NavigateToPage(loginPage);
|
||||
|
||||
LogoutIfAuthed();
|
||||
}
|
||||
@ -336,22 +351,16 @@ namespace Bit.iOS.ShareExtension
|
||||
private void LaunchLoginSsoFlow()
|
||||
{
|
||||
var loginPage = new LoginSsoPage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||
SetupAppAndApplyResources(loginPage);
|
||||
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||
{
|
||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(loginPage);
|
||||
var loginController = navigationPage.CreateViewController();
|
||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginController, true, null);
|
||||
NavigateToPage(loginPage);
|
||||
|
||||
LogoutIfAuthed();
|
||||
}
|
||||
@ -359,65 +368,97 @@ namespace Bit.iOS.ShareExtension
|
||||
private void LaunchTwoFactorFlow(bool authingWithSso)
|
||||
{
|
||||
var twoFactorPage = new TwoFactorPage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
||||
SetupAppAndApplyResources(twoFactorPage);
|
||||
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
||||
{
|
||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||
if (authingWithSso)
|
||||
{
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||
}
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(twoFactorPage);
|
||||
var twoFactorController = navigationPage.CreateViewController();
|
||||
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(twoFactorController, true, null);
|
||||
NavigateToPage(twoFactorPage);
|
||||
}
|
||||
|
||||
private void LaunchSetPasswordFlow()
|
||||
{
|
||||
var setPasswordPage = new SetPasswordPage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(setPasswordPage);
|
||||
SetupAppAndApplyResources(setPasswordPage);
|
||||
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
||||
{
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(setPasswordPage);
|
||||
var setPasswordController = navigationPage.CreateViewController();
|
||||
setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(setPasswordController, true, null);
|
||||
NavigateToPage(setPasswordPage);
|
||||
}
|
||||
|
||||
private void LaunchUpdateTempPasswordFlow()
|
||||
{
|
||||
var updateTempPasswordPage = new UpdateTempPasswordPage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesToPage(updateTempPasswordPage);
|
||||
SetupAppAndApplyResources(updateTempPasswordPage);
|
||||
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
|
||||
{
|
||||
vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.LogOutAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
vm.UpdateTempPasswordSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
vm.LogOutAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
NavigateToPage(updateTempPasswordPage);
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(updateTempPasswordPage);
|
||||
var updateTempPasswordController = navigationPage.CreateViewController();
|
||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(updateTempPasswordController, true, null);
|
||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||
{
|
||||
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
||||
{
|
||||
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 System;
|
||||
using UIKit;
|
||||
|
||||
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)
|
||||
: base(handle)
|
||||
{
|
||||
@ -17,24 +28,80 @@ namespace Bit.iOS.ShareExtension
|
||||
public override UINavigationItem BaseNavItem => _navItem;
|
||||
public override UIBarButtonItem BaseCancelButton => _cancelButton;
|
||||
public override UIBarButtonItem BaseSubmitButton => _submitButton;
|
||||
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
||||
public override Action Cancel => () => LoadingController.CompleteRequest();
|
||||
public override Action Success => () =>
|
||||
{
|
||||
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();
|
||||
|
||||
_cancelButton.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)
|
||||
{
|
||||
var task = CheckPasswordAsync();
|
||||
CheckPasswordAsync().FireAndForget();
|
||||
}
|
||||
|
||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||
{
|
||||
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")]
|
||||
partial class LockPasswordViewController
|
||||
{
|
||||
[Outlet]
|
||||
UIKit.UIBarButtonItem _accountSwitchingButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UIBarButtonItem _cancelButton { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UITableView _mainTableView { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UINavigationBar _navBar { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UINavigationItem _navItem { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UIView _overlayView { get; set; }
|
||||
|
||||
[Outlet]
|
||||
UIKit.UIBarButtonItem _submitButton { get; set; }
|
||||
|
||||
[Action ("AccountSwitchingButton_Activated:")]
|
||||
partial void AccountSwitchingButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
[Action ("CancelButton_Activated:")]
|
||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||
|
||||
@ -32,6 +44,11 @@ namespace Bit.iOS.ShareExtension
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (_accountSwitchingButton != null) {
|
||||
_accountSwitchingButton.Dispose ();
|
||||
_accountSwitchingButton = null;
|
||||
}
|
||||
|
||||
if (_cancelButton != null) {
|
||||
_cancelButton.Dispose ();
|
||||
_cancelButton = null;
|
||||
@ -47,10 +64,20 @@ namespace Bit.iOS.ShareExtension
|
||||
_navItem = null;
|
||||
}
|
||||
|
||||
if (_overlayView != null) {
|
||||
_overlayView.Dispose ();
|
||||
_overlayView = null;
|
||||
}
|
||||
|
||||
if (_submitButton != null) {
|
||||
_submitButton.Dispose ();
|
||||
_submitButton = null;
|
||||
}
|
||||
|
||||
if (_navBar != null) {
|
||||
_navBar.Dispose ();
|
||||
_navBar = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -11,10 +13,6 @@
|
||||
<scene sceneID="kFr-IN-5GS">
|
||||
<objects>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
@ -23,25 +21,25 @@
|
||||
<rect key="frame" x="66" y="352" width="282" height="44"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="jNx-Vd-K6U"/>
|
||||
<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"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="74l-Va-Vqa"/>
|
||||
<connections>
|
||||
<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>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yJx-cc-wzs" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-374" y="560"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<!--Extension Navigation Controller-->
|
||||
<scene sceneID="Wgx-vz-XqL">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
@ -54,60 +52,89 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1097" y="564"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="Tzp-2o-9k7">
|
||||
<!--Lock Password View Controller-->
|
||||
<scene sceneID="vQB-cT-8IC">
|
||||
<objects>
|
||||
<navigationController definesPresentationContext="YES" id="rh6-Mf-4Ja" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="UDq-kw-Ue7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||
<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"/>
|
||||
<viewController storyboardIdentifier="lockVC" id="Vi7-LV-nWW" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Vfd-7B-19G">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<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"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="85y-W9-d8q" id="3il-RO-S3K"/>
|
||||
<outlet property="delegate" destination="85y-W9-d8q" id="bLb-h4-pr3"/>
|
||||
</connections>
|
||||
<subviews>
|
||||
<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">
|
||||
<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"/>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Verify Master Password" id="qL3-iV-6Ld">
|
||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="d8j-HZ-erD">
|
||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ijE-Pa-OBq" userLabel="OverlayView">
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</barButtonItem>
|
||||
</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>
|
||||
<outlet property="_cancelButton" destination="d8j-HZ-erD" id="wlI-el-Snh"/>
|
||||
<outlet property="_mainTableView" destination="9on-wf-zdb" id="ltj-yY-5ue"/>
|
||||
<outlet property="_navItem" destination="qL3-iV-6Ld" id="Grb-Ta-NCF"/>
|
||||
<outlet property="_submitButton" destination="8a7-Vz-SJA" id="LS8-6Y-Wkp"/>
|
||||
<outlet property="_accountSwitchingButton" destination="nlD-Xn-HtM" id="SSG-zv-bAc"/>
|
||||
<outlet property="_cancelButton" destination="LrG-Qx-w4Q" id="aag-ZZ-Ifs"/>
|
||||
<outlet property="_mainTableView" destination="M1A-84-x5l" id="pA4-ao-Fhu"/>
|
||||
<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>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="5by-Sa-d9m" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Czu-9n-yKC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="335" y="1260"/>
|
||||
<point key="canvasLocation" x="403" y="560"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<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>
|
||||
</document>
|
||||
|
@ -26,7 +26,6 @@
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchArch>x86_64</MtouchArch>
|
||||
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
||||
<DeviceSpecificBuild>false</DeviceSpecificBuild>
|
||||
<MtouchVerbosity></MtouchVerbosity>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<AssemblyName>BitwardeniOSShareExtension</AssemblyName>
|
||||
@ -193,6 +192,10 @@
|
||||
<Compile Include="LockPasswordViewController.designer.cs">
|
||||
<DependentUpon>LockPasswordViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ExtensionNavigationController.cs" />
|
||||
<Compile Include="ExtensionNavigationController.designer.cs">
|
||||
<DependentUpon>ExtensionNavigationController.cs</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\App\App.csproj">
|
||||
|
Loading…
Reference in New Issue
Block a user