mirror of
https://github.com/bitwarden/mobile.git
synced 2024-09-29 04:07:37 +02:00
Merge branch 'master' into feature/totp-tab
# Conflicts: # src/App/Resources/AppResources.Designer.cs # src/App/Resources/AppResources.resx
This commit is contained in:
commit
1b82249d44
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -13,6 +13,11 @@ on:
|
|||||||
- Initial Release
|
- Initial Release
|
||||||
- Redeploy
|
- Redeploy
|
||||||
- Dry Run
|
- Dry Run
|
||||||
|
fdroid_publish:
|
||||||
|
description: 'Publish to f-droid store'
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@ -78,6 +83,7 @@ jobs:
|
|||||||
name: F-Droid Release
|
name: F-Droid Release
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
needs: release
|
needs: release
|
||||||
|
if: inputs.fdroid_publish
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||||
|
24
.github/workflows/version-bump.yml
vendored
24
.github/workflows/version-bump.yml
vendored
@ -19,12 +19,6 @@ jobs:
|
|||||||
- name: Create Version Branch
|
- name: Create Version Branch
|
||||||
run: |
|
run: |
|
||||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Checkout Version Branch
|
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
|
||||||
with:
|
|
||||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Bump Version - Android XML
|
- name: Bump Version - Android XML
|
||||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||||
@ -56,16 +50,32 @@ jobs:
|
|||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS/Info.plist"
|
file_path: "./src/iOS/Info.plist"
|
||||||
|
|
||||||
- name: Commit files
|
- name: Setup git
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
git config --local user.name "github-actions[bot]"
|
||||||
|
|
||||||
|
- name: Check if version changed
|
||||||
|
id: version-changed
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "::set-output name=changes_to_commit::TRUE"
|
||||||
|
else
|
||||||
|
echo "::set-output name=changes_to_commit::FALSE"
|
||||||
|
echo "No changes to commit!";
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Commit files
|
||||||
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
|
run: |
|
||||||
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
- name: Create Version PR
|
- name: Create Version PR
|
||||||
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
env:
|
env:
|
||||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
@ -54,6 +54,7 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||||
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
|
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
|
||||||
|
new Browser("com.iode.firefox", "mozac_browser_toolbar_url_view"),
|
||||||
new Browser("com.jamal2367.styx", "search"),
|
new Browser("com.jamal2367.styx", "search"),
|
||||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||||
new Browser("com.kiwibrowser.browser.dev", "url_bar"),
|
new Browser("com.kiwibrowser.browser.dev", "url_bar"),
|
||||||
@ -67,6 +68,7 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("com.naver.whale", "url_bar"),
|
new Browser("com.naver.whale", "url_bar"),
|
||||||
new Browser("com.opera.browser", "url_field"),
|
new Browser("com.opera.browser", "url_field"),
|
||||||
new Browser("com.opera.browser.beta", "url_field"),
|
new Browser("com.opera.browser.beta", "url_field"),
|
||||||
|
new Browser("com.opera.gx", "addressbarEdit"),
|
||||||
new Browser("com.opera.mini.native", "url_field"),
|
new Browser("com.opera.mini.native", "url_field"),
|
||||||
new Browser("com.opera.mini.native.beta", "url_field"),
|
new Browser("com.opera.mini.native.beta", "url_field"),
|
||||||
new Browser("com.opera.touch", "addressbarEdit"),
|
new Browser("com.opera.touch", "addressbarEdit"),
|
||||||
|
@ -73,6 +73,7 @@ namespace Bit.Droid.Autofill
|
|||||||
"com.google.android.apps.chrome",
|
"com.google.android.apps.chrome",
|
||||||
"com.google.android.apps.chrome_dev",
|
"com.google.android.apps.chrome_dev",
|
||||||
"com.google.android.captiveportallogin",
|
"com.google.android.captiveportallogin",
|
||||||
|
"com.iode.firefox",
|
||||||
"com.jamal2367.styx",
|
"com.jamal2367.styx",
|
||||||
"com.kiwibrowser.browser",
|
"com.kiwibrowser.browser",
|
||||||
"com.kiwibrowser.browser.dev",
|
"com.kiwibrowser.browser.dev",
|
||||||
@ -86,6 +87,7 @@ namespace Bit.Droid.Autofill
|
|||||||
"com.naver.whale",
|
"com.naver.whale",
|
||||||
"com.opera.browser",
|
"com.opera.browser",
|
||||||
"com.opera.browser.beta",
|
"com.opera.browser.beta",
|
||||||
|
"com.opera.gx",
|
||||||
"com.opera.mini.native",
|
"com.opera.mini.native",
|
||||||
"com.opera.mini.native.beta",
|
"com.opera.mini.native.beta",
|
||||||
"com.opera.touch",
|
"com.opera.touch",
|
||||||
|
@ -64,10 +64,11 @@ namespace Bit.Droid
|
|||||||
Intent?.Validate();
|
Intent?.Validate();
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
if (!CoreHelpers.InDebugMode())
|
|
||||||
|
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
||||||
{
|
{
|
||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
}
|
});
|
||||||
|
|
||||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ using System.Net;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.App.Controls;
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
@ -69,7 +70,8 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
|
ServiceContainer.Resolve<ILogger>("logger"));
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
@ -160,6 +162,7 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
|
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
|
@ -77,6 +77,9 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.google.android.captiveportallogin"
|
android:name="com.google.android.captiveportallogin"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.iode.firefox"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.jamal2367.styx"
|
android:name="com.jamal2367.styx"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
@ -116,6 +119,9 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.opera.browser.beta"
|
android:name="com.opera.browser.beta"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.opera.gx"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.opera.mini.native"
|
android:name="com.opera.mini.native"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
@ -948,5 +948,21 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
// for any Android-specific cleanup required after switching accounts
|
// for any Android-specific cleanup required after switching accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
|
{
|
||||||
|
if (CoreHelpers.ForceScreenCaptureEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var activity = CrossCurrentActivity.Current?.Activity;
|
||||||
|
if (await _stateService.GetScreenCaptureAllowedAsync())
|
||||||
|
{
|
||||||
|
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,6 @@ namespace Bit.App.Abstractions
|
|||||||
{
|
{
|
||||||
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
||||||
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
||||||
|
Task LogOutAsync(string userId, bool userInitiated, bool expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,5 +48,6 @@ namespace Bit.App.Abstractions
|
|||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
float GetSystemFontSizeScale();
|
float GetSystemFontSizeScale();
|
||||||
Task OnAccountSwitchCompleteAsync();
|
Task OnAccountSwitchCompleteAsync();
|
||||||
|
Task SetScreenCaptureAllowedAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,12 +129,10 @@
|
|||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
<Folder Include="Utilities\AccountManagement\" />
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
|
<Folder Include="Controls\DateTime\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
|
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -162,12 +160,6 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Update="Styles\Base.xaml">
|
|
||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
||||||
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
||||||
@ -422,5 +414,6 @@
|
|||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
<None Remove="Utilities\AccountManagement\" />
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
|
<None Remove="Controls\DateTime\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -301,7 +301,7 @@ namespace Bit.App
|
|||||||
UpdateThemeAsync();
|
UpdateThemeAsync();
|
||||||
};
|
};
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
|
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ namespace Bit.App.Controls
|
|||||||
public AccountViewCellViewModel(AccountView accountView)
|
public AccountViewCellViewModel(AccountView accountView)
|
||||||
{
|
{
|
||||||
AccountView = accountView;
|
AccountView = accountView;
|
||||||
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
|
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
||||||
|
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountView AccountView
|
public AccountView AccountView
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@ -50,7 +51,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
private Stream Draw()
|
private Stream Draw()
|
||||||
{
|
{
|
||||||
string chars = null;
|
string chars;
|
||||||
string upperData = null;
|
string upperData = null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_data))
|
if (string.IsNullOrEmpty(_data))
|
||||||
@ -71,62 +72,83 @@ namespace Bit.App.Controls
|
|||||||
var textColor = Color.White;
|
var textColor = Color.White;
|
||||||
var size = 50;
|
var size = 50;
|
||||||
|
|
||||||
var bitmap = new SKBitmap(
|
using (var bitmap = new SKBitmap(size * 2,
|
||||||
size * 2,
|
|
||||||
size * 2,
|
size * 2,
|
||||||
SKImageInfo.PlatformColorType,
|
SKImageInfo.PlatformColorType,
|
||||||
SKAlphaType.Premul);
|
SKAlphaType.Premul))
|
||||||
var canvas = new SKCanvas(bitmap);
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
IsAntialias = true,
|
using (var canvas = new SKCanvas(bitmap))
|
||||||
Style = SKPaintStyle.Fill,
|
{
|
||||||
StrokeJoin = SKStrokeJoin.Miter,
|
canvas.Clear(SKColors.Transparent);
|
||||||
Color = SKColor.Parse(bgColor.ToHex())
|
using (var paint = new SKPaint
|
||||||
};
|
{
|
||||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
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;
|
||||||
|
|
||||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
using (var circlePaint = new SKPaint
|
||||||
var textSize = midX / 1.3f;
|
{
|
||||||
var textPaint = new SKPaint
|
IsAntialias = true,
|
||||||
{
|
Style = SKPaintStyle.Fill,
|
||||||
IsAntialias = true,
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
Style = SKPaintStyle.Fill,
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
Color = SKColor.Parse(textColor.ToHex()),
|
})
|
||||||
TextSize = textSize,
|
{
|
||||||
TextAlign = SKTextAlign.Center,
|
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||||
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();
|
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||||
|
var textSize = midX / 1.3f;
|
||||||
|
using (var textPaint = new SKPaint
|
||||||
|
{
|
||||||
|
IsAntialias = true,
|
||||||
|
Style = SKPaintStyle.Fill,
|
||||||
|
Color = SKColor.Parse(textColor.ToHex()),
|
||||||
|
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);
|
||||||
|
|
||||||
|
using (var img = SKImage.FromBitmap(bitmap))
|
||||||
|
{
|
||||||
|
var data = img.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
return data?.AsStream(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFirstLetters(string data, int charCount)
|
private string GetFirstLetters(string data, int charCount)
|
||||||
{
|
{
|
||||||
var parts = data.Split();
|
var sanitizedData = data.Trim();
|
||||||
|
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
if (parts.Length > 1 && charCount <= 2)
|
if (parts.Length > 1 && charCount <= 2)
|
||||||
{
|
{
|
||||||
var text = "";
|
var text = string.Empty;
|
||||||
for (int i = 0; i < charCount; i++)
|
for (var i = 0; i < charCount; i++)
|
||||||
{
|
{
|
||||||
text += parts[i].Substring(0, 1);
|
text += parts[i][0];
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
if (data.Length > 2)
|
if (sanitizedData.Length > 2)
|
||||||
{
|
{
|
||||||
return data.Substring(0, 2);
|
return sanitizedData.Substring(0, 2);
|
||||||
}
|
}
|
||||||
return data;
|
return sanitizedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color StringToColor(string str)
|
private Color StringToColor(string str)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" />
|
<ToolbarItem Text="{u:I18n Save}" Command="{Binding SubmitCommand}" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
|
@ -36,14 +36,6 @@ namespace Bit.App.Pages
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Submit_Clicked(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (DoOnce())
|
|
||||||
{
|
|
||||||
await _vm.SubmitAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SubmitSuccessAsync()
|
private async Task SubmitSuccessAsync()
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class EnvironmentPageViewModel : BaseViewModel
|
public class EnvironmentPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
|
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
public EnvironmentPageViewModel()
|
public EnvironmentPageViewModel()
|
||||||
{
|
{
|
||||||
@ -22,10 +24,10 @@ namespace Bit.App.Pages
|
|||||||
IdentityUrl = _environmentService.IdentityUrl;
|
IdentityUrl = _environmentService.IdentityUrl;
|
||||||
IconsUrl = _environmentService.IconsUrl;
|
IconsUrl = _environmentService.IconsUrl;
|
||||||
NotificationsUrls = _environmentService.NotificationsUrl;
|
NotificationsUrls = _environmentService.NotificationsUrl;
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command SubmitCommand { get; }
|
public ICommand SubmitCommand { get; }
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
public string ApiUrl { get; set; }
|
public string ApiUrl { get; set; }
|
||||||
public string IdentityUrl { get; set; }
|
public string IdentityUrl { get; set; }
|
||||||
@ -37,6 +39,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
|
if (!ValidateUrls())
|
||||||
|
{
|
||||||
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
||||||
{
|
{
|
||||||
Base = BaseUrl,
|
Base = BaseUrl,
|
||||||
@ -57,5 +65,25 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
SubmitSuccessAction?.Invoke();
|
SubmitSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ValidateUrls()
|
||||||
|
{
|
||||||
|
bool IsUrlValid(string url)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsUrlValid(BaseUrl)
|
||||||
|
&& IsUrlValid(ApiUrl)
|
||||||
|
&& IsUrlValid(IdentityUrl)
|
||||||
|
&& IsUrlValid(WebVaultUrl)
|
||||||
|
&& IsUrlValid(IconsUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubmitException(Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@ -303,14 +303,14 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
|
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionDate}"
|
AutomationProperties.Name="{u:I18n DeletionDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding DeletionTime, Mode=TwoWay}"
|
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
@ -343,7 +343,7 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
|
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
|
||||||
PlaceHolder="mm/dd/yyyy"
|
PlaceHolder="mm/dd/yyyy"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
@ -351,7 +351,7 @@
|
|||||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
|
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
PlaceHolder="--:-- --"
|
PlaceHolder="--:-- --"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
@ -23,7 +23,6 @@ namespace Bit.App.Pages
|
|||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
private SendAddEditPageViewModel _vm;
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
public Action OnClose { get; set; }
|
|
||||||
public Action AfterSubmit { get; set; }
|
public Action AfterSubmit { get; set; }
|
||||||
|
|
||||||
public SendAddEditPage(
|
public SendAddEditPage(
|
||||||
@ -136,14 +135,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task CloseAsync()
|
private async Task CloseAsync()
|
||||||
{
|
{
|
||||||
if (OnClose is null)
|
await Navigation.PopModalAsync();
|
||||||
{
|
|
||||||
await Navigation.PopModalAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OnClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
@ -23,7 +24,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private bool _sendEnabled;
|
private bool _sendEnabled = true;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
private bool _emailVerified;
|
private bool _emailVerified;
|
||||||
private SendView _send;
|
private SendView _send;
|
||||||
@ -33,11 +34,7 @@ namespace Bit.App.Pages
|
|||||||
private int _deletionDateTypeSelectedIndex;
|
private int _deletionDateTypeSelectedIndex;
|
||||||
private int _expirationDateTypeSelectedIndex;
|
private int _expirationDateTypeSelectedIndex;
|
||||||
private DateTime _simpleDeletionDateTime;
|
private DateTime _simpleDeletionDateTime;
|
||||||
private DateTime _deletionDate;
|
|
||||||
private TimeSpan _deletionTime;
|
|
||||||
private DateTime? _simpleExpirationDateTime;
|
private DateTime? _simpleExpirationDateTime;
|
||||||
private DateTime? _expirationDate;
|
|
||||||
private TimeSpan? _expirationTime;
|
|
||||||
private bool _isOverridingPickers;
|
private bool _isOverridingPickers;
|
||||||
private int? _maxAccessCount;
|
private int? _maxAccessCount;
|
||||||
private string[] _additionalSendProperties = new[]
|
private string[] _additionalSendProperties = new[]
|
||||||
@ -89,8 +86,34 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
||||||
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
|
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DeletionDateTimeViewModel = new DateTimeViewModel(AppResources.DeletionDate, AppResources.DeletionTime);
|
||||||
|
ExpirationDateTimeViewModel = new DateTimeViewModel(AppResources.ExpirationDate, AppResources.ExpirationTime)
|
||||||
|
{
|
||||||
|
OnDateChanged = date =>
|
||||||
|
{
|
||||||
|
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Time.HasValue)
|
||||||
|
{
|
||||||
|
// auto-set time to current time upon setting date
|
||||||
|
ExpirationDateTimeViewModel.Time = DateTimeNow().TimeOfDay;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnTimeChanged = time =>
|
||||||
|
{
|
||||||
|
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Date.HasValue)
|
||||||
|
{
|
||||||
|
// auto-set date to current date upon setting time
|
||||||
|
ExpirationDateTimeViewModel.Date = DateTime.Today;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DatePlaceholder = "mm/dd/yyyy",
|
||||||
|
TimePlaceholder = "--:-- --"
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
public Command ToggleOptionsCommand { get; set; }
|
public Command ToggleOptionsCommand { get; set; }
|
||||||
public string SendId { get; set; }
|
public string SendId { get; set; }
|
||||||
@ -126,23 +149,14 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DateTime DeletionDate
|
|
||||||
{
|
|
||||||
get => _deletionDate;
|
|
||||||
set => SetProperty(ref _deletionDate, value);
|
|
||||||
}
|
|
||||||
public TimeSpan DeletionTime
|
|
||||||
{
|
|
||||||
get => _deletionTime;
|
|
||||||
set => SetProperty(ref _deletionTime, value);
|
|
||||||
}
|
|
||||||
public bool ShowOptions
|
public bool ShowOptions
|
||||||
{
|
{
|
||||||
get => _showOptions;
|
get => _showOptions;
|
||||||
set => SetProperty(ref _showOptions, value,
|
set => SetProperty(ref _showOptions, value,
|
||||||
additionalPropertyNames: new[]
|
additionalPropertyNames: new[]
|
||||||
{
|
{
|
||||||
nameof(OptionsAccessilibityText)
|
nameof(OptionsAccessilibityText),
|
||||||
|
nameof(OptionsShowHideIcon)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public int ExpirationDateTypeSelectedIndex
|
public int ExpirationDateTypeSelectedIndex
|
||||||
@ -156,28 +170,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DateTime? ExpirationDate
|
|
||||||
{
|
|
||||||
get => _expirationDate;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _expirationDate, value))
|
|
||||||
{
|
|
||||||
ExpirationDateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public TimeSpan? ExpirationTime
|
|
||||||
{
|
|
||||||
get => _expirationTime;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _expirationTime, value))
|
|
||||||
{
|
|
||||||
ExpirationTimeChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public int? MaxAccessCount
|
public int? MaxAccessCount
|
||||||
{
|
{
|
||||||
get => _maxAccessCount;
|
get => _maxAccessCount;
|
||||||
@ -205,7 +198,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
public string FileName
|
public string FileName
|
||||||
{
|
{
|
||||||
get => _fileName;
|
get => _fileName ?? AppResources.NoFileChosen;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _fileName, value))
|
if (SetProperty(ref _fileName, value))
|
||||||
@ -240,10 +233,13 @@ namespace Bit.App.Pages
|
|||||||
public bool IsFile => Send?.Type == SendType.File;
|
public bool IsFile => Send?.Type == SendType.File;
|
||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
|
public DateTimeViewModel DeletionDateTimeViewModel { get; }
|
||||||
|
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
|
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@ -268,10 +264,8 @@ namespace Bit.App.Pages
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Send = await send.DecryptAsync();
|
Send = await send.DecryptAsync();
|
||||||
DeletionDate = Send.DeletionDate.ToLocalTime();
|
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
|
||||||
DeletionTime = DeletionDate.TimeOfDay;
|
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
|
||||||
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
|
|
||||||
ExpirationTime = ExpirationDate?.TimeOfDay;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -280,8 +274,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Type = Type.GetValueOrDefault(defaultType),
|
Type = Type.GetValueOrDefault(defaultType),
|
||||||
};
|
};
|
||||||
_deletionDate = DateTimeNow().AddDays(7);
|
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
|
||||||
_deletionTime = DeletionDate.TimeOfDay;
|
|
||||||
DeletionDateTypeSelectedIndex = 4;
|
DeletionDateTypeSelectedIndex = 4;
|
||||||
ExpirationDateTypeSelectedIndex = 0;
|
ExpirationDateTypeSelectedIndex = 0;
|
||||||
}
|
}
|
||||||
@ -305,23 +298,22 @@ namespace Bit.App.Pages
|
|||||||
public void ClearExpirationDate()
|
public void ClearExpirationDate()
|
||||||
{
|
{
|
||||||
_isOverridingPickers = true;
|
_isOverridingPickers = true;
|
||||||
ExpirationDate = null;
|
ExpirationDateTimeViewModel.DateTime = null;
|
||||||
ExpirationTime = null;
|
|
||||||
_isOverridingPickers = false;
|
_isOverridingPickers = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSendData()
|
private void UpdateSendData()
|
||||||
{
|
{
|
||||||
// filename
|
// filename
|
||||||
if (Send.File != null && FileName != null)
|
if (Send.File != null && _fileName != null)
|
||||||
{
|
{
|
||||||
Send.File.FileName = FileName;
|
Send.File.FileName = _fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletion date
|
// deletion date
|
||||||
if (ShowDeletionCustomPickers)
|
if (ShowDeletionCustomPickers)
|
||||||
{
|
{
|
||||||
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
|
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -329,9 +321,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expiration date
|
// expiration date
|
||||||
if (ShowExpirationCustomPickers && ExpirationDate.HasValue && ExpirationTime.HasValue)
|
if (ShowExpirationCustomPickers && ExpirationDateTimeViewModel.DateTime.HasValue)
|
||||||
{
|
{
|
||||||
Send.ExpirationDate = ExpirationDate.Value.Date.Add(ExpirationTime.Value).ToUniversalTime();
|
Send.ExpirationDate = ExpirationDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
||||||
}
|
}
|
||||||
else if (_simpleExpirationDateTime.HasValue)
|
else if (_simpleExpirationDateTime.HasValue)
|
||||||
{
|
{
|
||||||
@ -484,7 +476,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
|
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
|
||||||
{
|
{
|
||||||
sendPage.OnClose();
|
sendPage.OnClose();
|
||||||
return;
|
return;
|
||||||
@ -625,24 +617,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExpirationDateChanged()
|
|
||||||
{
|
|
||||||
if (!_isOverridingPickers && !ExpirationTime.HasValue)
|
|
||||||
{
|
|
||||||
// auto-set time to current time upon setting date
|
|
||||||
ExpirationTime = DateTimeNow().TimeOfDay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpirationTimeChanged()
|
|
||||||
{
|
|
||||||
if (!_isOverridingPickers && !ExpirationDate.HasValue)
|
|
||||||
{
|
|
||||||
// auto-set date to current date upon setting time
|
|
||||||
ExpirationDate = DateTime.Today;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MaxAccessCountChanged()
|
private void MaxAccessCountChanged()
|
||||||
{
|
{
|
||||||
Send.MaxAccessCount = _maxAccessCount;
|
Send.MaxAccessCount = _maxAccessCount;
|
||||||
@ -666,5 +640,10 @@ namespace Bit.App.Pages
|
|||||||
DateTimeKind.Local
|
DateTimeKind.Local
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void TriggerSendTextPropertyChanged()
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Send)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
183
src/App/Pages/Send/SendAddOnlyOptionsView.xaml
Normal file
183
src/App/Pages/Send/SendAddOnlyOptionsView.xaml
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<ContentView
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
|
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||||
|
x:DataType="pages:SendAddEditPageViewModel"
|
||||||
|
x:Class="Bit.App.Pages.SendAddOnlyOptionsView">
|
||||||
|
<ContentView.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentView.Resources>
|
||||||
|
<ContentView.Content>
|
||||||
|
<StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DeletionDate}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Picker
|
||||||
|
x:Name="_deletionDateTypePicker"
|
||||||
|
ItemsSource="{Binding DeletionTypeOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding DeletionDateTypeSelectedIndex}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
ItemDisplayBinding="{Binding Key}"
|
||||||
|
ios:Picker.UpdateMode="WhenFinished"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n DeletionTime}" />
|
||||||
|
<controls:LazyDateTimePicker
|
||||||
|
x:Name="_lazyDeletionDateTimePicker"
|
||||||
|
BindingContext="{Binding DeletionDateTimeViewModel}"
|
||||||
|
IsVisible="{Binding ShowDeletionCustomPickers}"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DeletionDateInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout StyleClass="box-row" Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ExpirationDate}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Picker
|
||||||
|
x:Name="_expirationDateTypePicker"
|
||||||
|
ItemsSource="{Binding ExpirationTypeOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding ExpirationDateTypeSelectedIndex}"
|
||||||
|
ItemDisplayBinding="{Binding Key}"
|
||||||
|
ios:Picker.UpdateMode="WhenFinished"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ExpirationTime}" />
|
||||||
|
<controls:LazyDateTimePicker
|
||||||
|
x:Name="_lazyExpirationDateTimePicker"
|
||||||
|
BindingContext="{Binding ExpirationDateTimeViewModel}"
|
||||||
|
IsVisible="{Binding ShowExpirationCustomPickers}"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ExpirationDateInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MaximumAccessCount}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Entry
|
||||||
|
Text="{Binding MaxAccessCount}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Keyboard="Numeric"
|
||||||
|
MaxLength="9"
|
||||||
|
TextChanged="OnMaxAccessCountTextChanged"
|
||||||
|
HorizontalOptions="FillAndExpand" />
|
||||||
|
<controls:ExtendedStepper
|
||||||
|
x:Name="_maxAccessCountStepper"
|
||||||
|
Value="{Binding MaxAccessCount}"
|
||||||
|
Maximum="999999999"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n MaximumAccessCountInfo}"
|
||||||
|
StyleClass="box-footer-label" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NewPassword}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout Orientation="Horizontal">
|
||||||
|
<Entry
|
||||||
|
Text="{Binding NewPassword}"
|
||||||
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
HorizontalOptions="FillAndExpand" />
|
||||||
|
<controls:IconButton
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowPasswordIcon}"
|
||||||
|
Command="{Binding TogglePasswordCommand}"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Notes}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Editor
|
||||||
|
x:Name="_notesEditor"
|
||||||
|
AutoSize="TextChanges"
|
||||||
|
Text="{Binding Send.Notes}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Margin="0,10,0,5"
|
||||||
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
|
<Editor.Effects>
|
||||||
|
<effects:ScrollEnabledEffect />
|
||||||
|
</Editor.Effects>
|
||||||
|
</Editor>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="box-row-separator" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NotesInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n HideEmail}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.HideEmail}"
|
||||||
|
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DisableSend}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.Disabled}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</ContentView.Content>
|
||||||
|
</ContentView>
|
91
src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
Normal file
91
src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Behaviors;
|
||||||
|
using Xamarin.CommunityToolkit.UI.Views;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class SendAddOnlyOptionsView : ContentView
|
||||||
|
{
|
||||||
|
public SendAddOnlyOptionsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendAddEditPageViewModel ViewModel => BindingContext as SendAddEditPageViewModel;
|
||||||
|
|
||||||
|
public void SetMainScrollView(ScrollView scrollView)
|
||||||
|
{
|
||||||
|
_notesEditor.Behaviors.Add(new EditorPreventAutoBottomScrollingOnFocusedBehavior { ParentScrollView = scrollView });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMaxAccessCountTextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(e.NewTextValue))
|
||||||
|
{
|
||||||
|
ViewModel.MaxAccessCount = null;
|
||||||
|
_maxAccessCountStepper.Value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// accept only digits
|
||||||
|
if (!int.TryParse(e.NewTextValue, out int _))
|
||||||
|
{
|
||||||
|
((Entry)sender).Text = e.OldTextValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(propertyName);
|
||||||
|
|
||||||
|
if (propertyName == nameof(BindingContext)
|
||||||
|
&&
|
||||||
|
ViewModel != null)
|
||||||
|
{
|
||||||
|
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_lazyDeletionDateTimePicker.IsLoaded
|
||||||
|
&&
|
||||||
|
e.PropertyName == nameof(SendAddEditPageViewModel.ShowDeletionCustomPickers)
|
||||||
|
&&
|
||||||
|
ViewModel.ShowDeletionCustomPickers)
|
||||||
|
{
|
||||||
|
_lazyDeletionDateTimePicker.LoadViewAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lazyExpirationDateTimePicker.IsLoaded
|
||||||
|
&&
|
||||||
|
e.PropertyName == nameof(SendAddEditPageViewModel.ShowExpirationCustomPickers)
|
||||||
|
&&
|
||||||
|
ViewModel.ShowExpirationCustomPickers)
|
||||||
|
{
|
||||||
|
_lazyExpirationDateTimePicker.LoadViewAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SendAddOnlyOptionsLazyView : LazyView<SendAddOnlyOptionsView>
|
||||||
|
{
|
||||||
|
public ScrollView MainScrollView { get; set; }
|
||||||
|
|
||||||
|
public override async ValueTask LoadViewAsync()
|
||||||
|
{
|
||||||
|
await base.LoadViewAsync();
|
||||||
|
|
||||||
|
if (Content is SendAddOnlyOptionsView optionsView)
|
||||||
|
{
|
||||||
|
optionsView.SetMainScrollView(MainScrollView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
190
src/App/Pages/Send/SendAddOnlyPage.xaml
Normal file
190
src/App/Pages/Send/SendAddOnlyPage.xaml
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<pages:BaseContentPage
|
||||||
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Bit.App.Pages.SendAddOnlyPage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
|
||||||
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
|
x:DataType="pages:SendAddEditPageViewModel"
|
||||||
|
x:Name="_page"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:SendAddEditPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<!--Order matters here or the avatar's image won't be updated correctly, check iOS CustomNavigationRenderer for more info-->
|
||||||
|
<controls:ExtendedToolbarItem
|
||||||
|
x:Name="_accountAvatar"
|
||||||
|
IconImageSource="{Binding AvatarImageSource}"
|
||||||
|
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
|
||||||
|
Order="Primary"
|
||||||
|
Priority="-2"
|
||||||
|
UseOriginalImage="True"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
||||||
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<AbsoluteLayout>
|
||||||
|
<ScrollView
|
||||||
|
x:Name="_scrollView"
|
||||||
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
|
AbsoluteLayout.LayoutFlags="All">
|
||||||
|
<StackLayout x:Name="_mainContainer" StyleClass="box">
|
||||||
|
<Frame
|
||||||
|
IsVisible="{Binding SendEnabled, Converter={StaticResource inverseBool}}"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n SendDisabledWarning}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</Frame>
|
||||||
|
<Frame
|
||||||
|
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n SendOptionsPolicyInEffect}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</Frame>
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Name}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Entry
|
||||||
|
x:Name="_nameEntry"
|
||||||
|
Text="{Binding Send.Name}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NameInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
IsVisible="{Binding IsFile}">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeFile}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row">
|
||||||
|
<Label
|
||||||
|
Text="{Binding FileName}"
|
||||||
|
LineBreakMode="CharacterWrap"
|
||||||
|
StyleClass="text-sm, text-muted"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
<Label
|
||||||
|
Margin="0, 5, 0, 0"
|
||||||
|
Text="{u:I18n MaxFileSize}"
|
||||||
|
StyleClass="text-sm, text-muted"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row"
|
||||||
|
IsVisible="{Binding IsText}">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeText}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Editor
|
||||||
|
x:Name="_textEditor"
|
||||||
|
AutoSize="TextChanges"
|
||||||
|
Text="{Binding Send.Text.Text}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Margin="{Binding EditorMargins}"
|
||||||
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
|
<Editor.Behaviors>
|
||||||
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
|
</Editor.Behaviors>
|
||||||
|
<Editor.Effects>
|
||||||
|
<effects:ScrollEnabledEffect />
|
||||||
|
</Editor.Effects>
|
||||||
|
</Editor>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="box-row-separator" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n TypeTextInfo}"
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Margin="0,5,0,10" />
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n HideTextByDefault}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.Text.Hidden}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch">
|
||||||
|
<Label
|
||||||
|
Text="{Binding ShareOnSaveText}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding ShareOnSave}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
|
||||||
|
<StackLayout.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Tapped="OptionsHeader_Tapped" />
|
||||||
|
</StackLayout.GestureRecognizers>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n Options}"
|
||||||
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
|
Margin="0,0,5,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
|
<controls:IconLabel
|
||||||
|
Text="{Binding OptionsShowHideIcon}"
|
||||||
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
|
</StackLayout>
|
||||||
|
<pages:SendAddOnlyOptionsLazyView x:Name="_lazyOptionsView" IsVisible="{Binding ShowOptions}" />
|
||||||
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<controls:AccountSwitchingOverlayView
|
||||||
|
x:Name="_accountListOverlay"
|
||||||
|
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
|
||||||
|
AbsoluteLayout.LayoutFlags="All"
|
||||||
|
LongPressAccountEnabled="False"
|
||||||
|
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
|
||||||
|
</AbsoluteLayout>
|
||||||
|
|
||||||
|
</pages:BaseContentPage>
|
178
src/App/Pages/Send/SendAddOnlyPage.xaml.cs
Normal file
178
src/App/Pages/Send/SendAddOnlyPage.xaml.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a version of <see cref="SendAddEditPage"/> that is reduced for adding only and adapted
|
||||||
|
/// for performance for iOS Share extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should NOT be used in Android.
|
||||||
|
/// </remarks>
|
||||||
|
public partial class SendAddOnlyPage : BaseContentPage
|
||||||
|
{
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
|
private AppOptions _appOptions;
|
||||||
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
|
public Action OnClose { get; set; }
|
||||||
|
public Action AfterSubmit { get; set; }
|
||||||
|
|
||||||
|
public SendAddOnlyPage(
|
||||||
|
AppOptions appOptions = null,
|
||||||
|
string sendId = null,
|
||||||
|
SendType? type = null)
|
||||||
|
{
|
||||||
|
if (appOptions?.IosExtension != true)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(nameof(SendAddOnlyPage) + " is only prepared to be used in iOS share extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_appOptions = appOptions;
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as SendAddEditPageViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
_vm.SendId = sendId;
|
||||||
|
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
|
||||||
|
|
||||||
|
if (_vm.IsText)
|
||||||
|
{
|
||||||
|
_nameEntry.ReturnType = ReturnType.Next;
|
||||||
|
_nameEntry.ReturnCommand = new Command(() => _textEditor.Focus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
|
||||||
|
{
|
||||||
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
|
}
|
||||||
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _vm.InitAsync();
|
||||||
|
|
||||||
|
if (!await _vm.LoadAsync())
|
||||||
|
{
|
||||||
|
await CloseAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accountAvatar?.OnAppearing();
|
||||||
|
await Device.InvokeOnMainThreadAsync(async () => _vm.AvatarImageSource = await GetAvatarImageSourceAsync());
|
||||||
|
|
||||||
|
await HandleCreateRequest();
|
||||||
|
if (string.IsNullOrWhiteSpace(_vm.Send?.Name))
|
||||||
|
{
|
||||||
|
RequestFocus(_nameEntry);
|
||||||
|
}
|
||||||
|
AdjustToolbar();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
await CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_accountAvatar?.OnDisappearing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CloseAsync()
|
||||||
|
{
|
||||||
|
if (OnClose is null)
|
||||||
|
{
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Save_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
var submitted = await _vm.SubmitAsync();
|
||||||
|
if (submitted)
|
||||||
|
{
|
||||||
|
AfterSubmit?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdjustToolbar()
|
||||||
|
{
|
||||||
|
_saveItem.IsEnabled = _vm.SendEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task HandleCreateRequest()
|
||||||
|
{
|
||||||
|
if (_appOptions?.CreateSend == null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vm.IsAddFromShare = true;
|
||||||
|
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
|
||||||
|
|
||||||
|
var name = _appOptions.CreateSend.Item2;
|
||||||
|
_vm.Send.Name = name;
|
||||||
|
|
||||||
|
var type = _appOptions.CreateSend.Item1;
|
||||||
|
if (type == SendType.File)
|
||||||
|
{
|
||||||
|
_vm.FileData = _appOptions.CreateSend.Item3;
|
||||||
|
_vm.FileName = name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var text = _appOptions.CreateSend.Item4;
|
||||||
|
_vm.Send.Text.Text = text;
|
||||||
|
_vm.TriggerSendTextPropertyChanged();
|
||||||
|
}
|
||||||
|
_appOptions.CreateSend = null;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OptionsHeader_Tapped(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_vm.ToggleOptionsCommand.Execute(null);
|
||||||
|
|
||||||
|
if (!_lazyOptionsView.IsLoaded)
|
||||||
|
{
|
||||||
|
_lazyOptionsView.MainScrollView = _scrollView;
|
||||||
|
_lazyOptionsView.LoadViewAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -83,31 +83,31 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableAutoTotpCopy}"
|
Text="{u:I18n CopyTotpAutomatically}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding DisableAutoTotpCopy}"
|
IsToggled="{Binding AutoTotpCopy}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableAutoTotpCopyDescription}"
|
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableWebsiteIcons}"
|
Text="{u:I18n ShowWebsiteIcons}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding DisableFavicon}"
|
IsToggled="{Binding Favicon}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableWebsiteIconsDescription}"
|
Text="{u:I18n ShowWebsiteIconsDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
@ -117,16 +117,16 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableSavePrompt}"
|
Text="{u:I18n AskToAddLogin}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AutofillDisableSavePrompt}"
|
IsToggled="{Binding AutofillSavePrompt}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisableSavePromptDescription}"
|
Text="{u:I18n AskToAddLoginDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
|
@ -12,15 +12,14 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class OptionsPageViewModel : BaseViewModel
|
public class OptionsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly ITotpService _totpService;
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
|
|
||||||
|
|
||||||
private bool _autofillDisableSavePrompt;
|
private bool _autofillSavePrompt;
|
||||||
private string _autofillBlacklistedUris;
|
private string _autofillBlacklistedUris;
|
||||||
private bool _disableFavicon;
|
private bool _favicon;
|
||||||
private bool _disableAutoTotpCopy;
|
private bool _autoTotpCopy;
|
||||||
private int _clearClipboardSelectedIndex;
|
private int _clearClipboardSelectedIndex;
|
||||||
private int _themeSelectedIndex;
|
private int _themeSelectedIndex;
|
||||||
private int _autoDarkThemeSelectedIndex;
|
private int _autoDarkThemeSelectedIndex;
|
||||||
@ -31,7 +30,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public OptionsPageViewModel()
|
public OptionsPageViewModel()
|
||||||
{
|
{
|
||||||
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
|
||||||
@ -133,38 +131,38 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisableFavicon
|
public bool Favicon
|
||||||
{
|
{
|
||||||
get => _disableFavicon;
|
get => _favicon;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _disableFavicon, value))
|
if (SetProperty(ref _favicon, value))
|
||||||
{
|
{
|
||||||
UpdateDisableFaviconAsync().FireAndForget();
|
UpdateFaviconAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisableAutoTotpCopy
|
public bool AutoTotpCopy
|
||||||
{
|
{
|
||||||
get => _disableAutoTotpCopy;
|
get => _autoTotpCopy;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _disableAutoTotpCopy, value))
|
if (SetProperty(ref _autoTotpCopy, value))
|
||||||
{
|
{
|
||||||
UpdateAutoTotpCopyAsync().FireAndForget();
|
UpdateAutoTotpCopyAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AutofillDisableSavePrompt
|
public bool AutofillSavePrompt
|
||||||
{
|
{
|
||||||
get => _autofillDisableSavePrompt;
|
get => _autofillSavePrompt;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _autofillDisableSavePrompt, value))
|
if (SetProperty(ref _autofillSavePrompt, value))
|
||||||
{
|
{
|
||||||
UpdateAutofillDisableSavePromptAsync().FireAndForget();
|
UpdateAutofillSavePromptAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,11 +181,11 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
AutofillDisableSavePrompt = (await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
||||||
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||||
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
|
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
|
||||||
DisableAutoTotpCopy = !(await _totpService.IsAutoCopyEnabledAsync());
|
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
||||||
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
var theme = await _stateService.GetThemeAsync();
|
var theme = await _stateService.GetThemeAsync();
|
||||||
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
||||||
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
|
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
|
||||||
@ -204,15 +202,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
await _stateService.SetDisableAutoTotpCopyAsync(DisableAutoTotpCopy);
|
// TODO: [PS-961] Fix negative function names
|
||||||
|
await _stateService.SetDisableAutoTotpCopyAsync(!AutoTotpCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateDisableFaviconAsync()
|
private async Task UpdateFaviconAsync()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
await _stateService.SetDisableFaviconAsync(DisableFavicon);
|
// TODO: [PS-961] Fix negative function names
|
||||||
|
await _stateService.SetDisableFaviconAsync(!Favicon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,11 +243,12 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateAutofillDisableSavePromptAsync()
|
private async Task UpdateAutofillSavePromptAsync()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
await _stateService.SetAutofillDisableSavePromptAsync(AutofillDisableSavePrompt);
|
// TODO: [PS-961] Fix negative function names
|
||||||
|
await _stateService.SetAutofillDisableSavePromptAsync(!AutofillSavePrompt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,13 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Pages.Accounts;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class SettingsPage : BaseContentPage
|
public partial class SettingsPage : BaseContentPage
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
|
||||||
private readonly TabsPage _tabsPage;
|
private readonly TabsPage _tabsPage;
|
||||||
private SettingsPageViewModel _vm;
|
private SettingsPageViewModel _vm;
|
||||||
|
|
||||||
@ -21,7 +16,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_tabsPage = tabsPage;
|
_tabsPage = tabsPage;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
||||||
_vm = BindingContext as SettingsPageViewModel;
|
_vm = BindingContext as SettingsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
}
|
}
|
||||||
@ -67,122 +61,12 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
private void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
((ExtendedCollectionView)sender).SelectedItem = null;
|
((ExtendedCollectionView)sender).SelectedItem = null;
|
||||||
if (!DoOnce())
|
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item)
|
||||||
{
|
{
|
||||||
return;
|
_vm?.ExecuteSettingItemCommand.Execute(item);
|
||||||
}
|
|
||||||
if (!(e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Name == AppResources.Sync)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new SyncPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.AutofillServices)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(this)));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.PasswordAutofill)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new AutofillPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.AppExtension)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.Options)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new OptionsPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.Folders)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new FoldersPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.About)
|
|
||||||
{
|
|
||||||
await _vm.AboutAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.HelpAndFeedback)
|
|
||||||
{
|
|
||||||
_vm.Help();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.FingerprintPhrase)
|
|
||||||
{
|
|
||||||
await _vm.FingerprintAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.RateTheApp)
|
|
||||||
{
|
|
||||||
_vm.Rate();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.ImportItems)
|
|
||||||
{
|
|
||||||
_vm.Import();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.ExportVault)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.LearnOrg)
|
|
||||||
{
|
|
||||||
await _vm.ShareAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.WebVault)
|
|
||||||
{
|
|
||||||
_vm.WebVault();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.ChangeMasterPassword)
|
|
||||||
{
|
|
||||||
await _vm.ChangePasswordAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.TwoStepLogin)
|
|
||||||
{
|
|
||||||
await _vm.TwoStepAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.LogOut)
|
|
||||||
{
|
|
||||||
await _vm.LogOutAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.DeleteAccount)
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()));
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.LockNow)
|
|
||||||
{
|
|
||||||
await _vm.LockAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.VaultTimeout)
|
|
||||||
{
|
|
||||||
await _vm.VaultTimeoutAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.VaultTimeoutAction)
|
|
||||||
{
|
|
||||||
await _vm.VaultTimeoutActionAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.UnlockWithPIN)
|
|
||||||
{
|
|
||||||
await _vm.UpdatePinAsync();
|
|
||||||
}
|
|
||||||
else if (item.Name == AppResources.SubmitCrashLogs)
|
|
||||||
{
|
|
||||||
await _vm.LoggerReportingAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var biometricName = AppResources.Biometrics;
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
|
||||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
|
||||||
biometricName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
|
|
||||||
}
|
|
||||||
if (item.Name == string.Format(AppResources.UnlockWith, biometricName))
|
|
||||||
{
|
|
||||||
await _vm.UpdateBiometricAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@ -12,6 +13,8 @@ namespace Bit.App.Pages
|
|||||||
public string SubLabel { get; set; }
|
public string SubLabel { get; set; }
|
||||||
public TimeSpan? Time { get; set; }
|
public TimeSpan? Time { get; set; }
|
||||||
public bool UseFrame { get; set; }
|
public bool UseFrame { get; set; }
|
||||||
|
public Func<Task> ExecuteAsync { get; set; }
|
||||||
|
|
||||||
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
|
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
|
||||||
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
|
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
|
||||||
public bool ShowSubLabel => SubLabel.Length != 0;
|
public bool ShowSubLabel => SubLabel.Length != 0;
|
||||||
|
@ -3,11 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Pages.Accounts;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@ -30,11 +30,13 @@ namespace Bit.App.Pages
|
|||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly ILogger _loggerService;
|
private readonly ILogger _loggerService;
|
||||||
|
|
||||||
private const int CustomVaultTimeoutValue = -100;
|
private const int CustomVaultTimeoutValue = -100;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
private bool _pin;
|
private bool _pin;
|
||||||
private bool _biometric;
|
private bool _biometric;
|
||||||
|
private bool _screenCaptureAllowed;
|
||||||
private string _lastSyncDate;
|
private string _lastSyncDate;
|
||||||
private string _vaultTimeoutDisplayValue;
|
private string _vaultTimeoutDisplayValue;
|
||||||
private string _vaultTimeoutActionDisplayValue;
|
private string _vaultTimeoutActionDisplayValue;
|
||||||
@ -84,10 +86,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
|
|
||||||
|
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
|
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||||
@ -117,6 +123,7 @@ namespace Bit.App.Pages
|
|||||||
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
_pin = pinSet.Item1 || pinSet.Item2;
|
_pin = pinSet.Item1 || pinSet.Item2;
|
||||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||||
|
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
||||||
|
|
||||||
if (_vaultTimeoutDisplayValue == null)
|
if (_vaultTimeoutDisplayValue == null)
|
||||||
{
|
{
|
||||||
@ -434,6 +441,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public void BuildList()
|
public void BuildList()
|
||||||
{
|
{
|
||||||
|
//TODO: Refactor this once navigation is abstracted so that it doesn't depend on Page, e.g. Page.Navigation.PushModalAsync...
|
||||||
|
|
||||||
var doUpper = Device.RuntimePlatform != Device.Android;
|
var doUpper = Device.RuntimePlatform != Device.Android;
|
||||||
var autofillItems = new List<SettingsPageListItem>();
|
var autofillItems = new List<SettingsPageListItem>();
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@ -441,38 +450,69 @@ namespace Bit.App.Pages
|
|||||||
autofillItems.Add(new SettingsPageListItem
|
autofillItems.Add(new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.AutofillServices,
|
Name = AppResources.AutofillServices,
|
||||||
SubLabel = _deviceActionService.AutofillServicesEnabled() ?
|
SubLabel = _deviceActionService.AutofillServicesEnabled() ? AppResources.Enabled : AppResources.Disabled,
|
||||||
AppResources.Enabled : AppResources.Disabled
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(Page as SettingsPage)))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
autofillItems.Add(new SettingsPageListItem { Name = AppResources.PasswordAutofill });
|
autofillItems.Add(new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.PasswordAutofill,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
autofillItems.Add(new SettingsPageListItem { Name = AppResources.AppExtension });
|
autofillItems.Add(new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.AppExtension,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
var manageItems = new List<SettingsPageListItem>
|
var manageItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.Folders },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.Sync, SubLabel = _lastSyncDate }
|
{
|
||||||
|
Name = AppResources.Folders,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage()))
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.Sync,
|
||||||
|
SubLabel = _lastSyncDate,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new SyncPage()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var securityItems = new List<SettingsPageListItem>
|
var securityItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.VaultTimeout,
|
||||||
|
SubLabel = _vaultTimeoutDisplayValue,
|
||||||
|
ExecuteAsync = () => VaultTimeoutAsync() },
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.VaultTimeoutAction,
|
Name = AppResources.VaultTimeoutAction,
|
||||||
SubLabel = _vaultTimeoutActionDisplayValue
|
SubLabel = _vaultTimeoutActionDisplayValue,
|
||||||
|
ExecuteAsync = () => VaultTimeoutActionAsync()
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.UnlockWithPIN,
|
Name = AppResources.UnlockWithPIN,
|
||||||
SubLabel = _pin ? AppResources.Enabled : AppResources.Disabled
|
SubLabel = _pin ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
ExecuteAsync = () => UpdatePinAsync()
|
||||||
},
|
},
|
||||||
new SettingsPageListItem { Name = AppResources.LockNow },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.TwoStepLogin }
|
{
|
||||||
|
Name = AppResources.LockNow,
|
||||||
|
ExecuteAsync = () => LockAsync()
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.TwoStepLogin,
|
||||||
|
ExecuteAsync = () => TwoStepAsync()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (_supportsBiometric || _biometric)
|
if (_supportsBiometric || _biometric)
|
||||||
{
|
{
|
||||||
@ -485,7 +525,8 @@ namespace Bit.App.Pages
|
|||||||
var item = new SettingsPageListItem
|
var item = new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = string.Format(AppResources.UnlockWith, biometricName),
|
Name = string.Format(AppResources.UnlockWith, biometricName),
|
||||||
SubLabel = _biometric ? AppResources.Enabled : AppResources.Disabled
|
SubLabel = _biometric ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
ExecuteAsync = () => UpdateBiometricAsync()
|
||||||
};
|
};
|
||||||
securityItems.Insert(2, item);
|
securityItems.Insert(2, item);
|
||||||
}
|
}
|
||||||
@ -508,40 +549,98 @@ namespace Bit.App.Pages
|
|||||||
UseFrame = true,
|
UseFrame = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
securityItems.Add(new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.AllowScreenCapture,
|
||||||
|
SubLabel = _screenCaptureAllowed ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
ExecuteAsync = () => SetScreenCaptureAllowedAsync()
|
||||||
|
});
|
||||||
|
}
|
||||||
var accountItems = new List<SettingsPageListItem>
|
var accountItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.LogOut }
|
{
|
||||||
|
Name = AppResources.FingerprintPhrase,
|
||||||
|
ExecuteAsync = () => FingerprintAsync()
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.LogOut,
|
||||||
|
ExecuteAsync = () => LogOutAsync()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (_showChangeMasterPassword)
|
if (_showChangeMasterPassword)
|
||||||
{
|
{
|
||||||
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
|
accountItems.Insert(0, new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.ChangeMasterPassword,
|
||||||
|
ExecuteAsync = () => ChangePasswordAsync()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
var toolsItems = new List<SettingsPageListItem>
|
var toolsItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.ImportItems },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.ExportVault }
|
{
|
||||||
|
Name = AppResources.ImportItems,
|
||||||
|
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Import())
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.ExportVault,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (IncludeLinksWithSubscriptionInfo())
|
if (IncludeLinksWithSubscriptionInfo())
|
||||||
{
|
{
|
||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
toolsItems.Add(new SettingsPageListItem
|
||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
{
|
||||||
|
Name = AppResources.LearnOrg,
|
||||||
|
ExecuteAsync = () => ShareAsync()
|
||||||
|
});
|
||||||
|
toolsItems.Add(new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.WebVault,
|
||||||
|
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => WebVault())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherItems = new List<SettingsPageListItem>
|
var otherItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.Options },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.About },
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
Name = AppResources.Options,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new OptionsPage()))
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.About,
|
||||||
|
ExecuteAsync = () => AboutAsync()
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.HelpAndFeedback,
|
||||||
|
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Help())
|
||||||
|
},
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.SubmitCrashLogs,
|
Name = AppResources.SubmitCrashLogs,
|
||||||
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
ExecuteAsync = () => LoggerReportingAsync()
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
new SettingsPageListItem
|
||||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
{
|
||||||
|
Name = AppResources.RateTheApp,
|
||||||
|
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Rate())
|
||||||
|
},
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.DeleteAccount,
|
||||||
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
||||||
@ -621,5 +720,33 @@ namespace Bit.App.Pages
|
|||||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||||
|
|
||||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||||
|
|
||||||
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
|
{
|
||||||
|
if (CoreHelpers.ForceScreenCaptureEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_screenCaptureAllowed
|
||||||
|
&&
|
||||||
|
!await Page.DisplayAlert(AppResources.AllowScreenCapture, AppResources.AreYouSureYouWantToEnableScreenCapture, AppResources.Yes, AppResources.No))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _stateService.SetScreenCaptureAllowedAsync(!_screenCaptureAllowed);
|
||||||
|
_screenCaptureAllowed = !_screenCaptureAllowed;
|
||||||
|
await _deviceActionService.SetScreenCaptureAllowedAsync();
|
||||||
|
BuildList();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_loggerService.Exception(ex);
|
||||||
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,8 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget();
|
||||||
|
|
||||||
await InitVaultFilterAsync(MainPage);
|
await InitVaultFilterAsync(MainPage);
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
|
@ -718,7 +718,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (IsBooleanType)
|
if (IsBooleanType)
|
||||||
{
|
{
|
||||||
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
|
return _field.Value == "true" ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
||||||
}
|
}
|
||||||
else if (IsLinkedType)
|
else if (IsLinkedType)
|
||||||
{
|
{
|
||||||
|
48
src/App/Resources/AppResources.Designer.cs
generated
48
src/App/Resources/AppResources.Designer.cs
generated
@ -1481,15 +1481,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableAutoTotpCopyDescription {
|
public static string CopyTotpAutomaticallyDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableAutoTotpCopyDescription", resourceCulture);
|
return ResourceManager.GetString("CopyTotpAutomaticallyDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableAutoTotpCopy {
|
public static string CopyTotpAutomatically {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableAutoTotpCopy", resourceCulture);
|
return ResourceManager.GetString("CopyTotpAutomatically", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1919,15 +1919,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableWebsiteIcons {
|
public static string ShowWebsiteIcons {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableWebsiteIcons", resourceCulture);
|
return ResourceManager.GetString("ShowWebsiteIcons", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableWebsiteIconsDescription {
|
public static string ShowWebsiteIconsDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableWebsiteIconsDescription", resourceCulture);
|
return ResourceManager.GetString("ShowWebsiteIconsDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2759,15 +2759,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableSavePrompt {
|
public static string AskToAddLogin {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableSavePrompt", resourceCulture);
|
return ResourceManager.GetString("AskToAddLogin", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DisableSavePromptDescription {
|
public static string AskToAddLoginDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DisableSavePromptDescription", resourceCulture);
|
return ResourceManager.GetString("AskToAddLoginDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4120,5 +4120,29 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("AuthenticationCodesListIsVisibleActivateToShowCipherList", resourceCulture);
|
return ResourceManager.GetString("AuthenticationCodesListIsVisibleActivateToShowCipherList", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string EnvironmentPageUrlsError {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnvironmentPageUrlsError", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenericErrorMessage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("GenericErrorMessage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AllowScreenCapture {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AllowScreenCapture", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AreYouSureYouWantToEnableScreenCapture {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AreYouSureYouWantToEnableScreenCapture", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
@ -236,7 +236,7 @@
|
|||||||
<comment>The button text that allows user to launch the website to their web browser.</comment>
|
<comment>The button text that allows user to launch the website to their web browser.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="HelpAndFeedback" xml:space="preserve">
|
<data name="HelpAndFeedback" xml:space="preserve">
|
||||||
<value>Help and Feedback</value>
|
<value>Help and feedback</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Hide" xml:space="preserve">
|
<data name="Hide" xml:space="preserve">
|
||||||
<value>Hide</value>
|
<value>Hide</value>
|
||||||
@ -269,7 +269,7 @@
|
|||||||
<comment>Title for login page. (noun)</comment>
|
<comment>Title for login page. (noun)</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogOut" xml:space="preserve">
|
<data name="LogOut" xml:space="preserve">
|
||||||
<value>Log Out</value>
|
<value>Log out</value>
|
||||||
<comment>The log out button text (verb).</comment>
|
<comment>The log out button text (verb).</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogoutConfirmation" xml:space="preserve">
|
<data name="LogoutConfirmation" xml:space="preserve">
|
||||||
@ -416,7 +416,7 @@
|
|||||||
<value>Add an Item</value>
|
<value>Add an Item</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AppExtension" xml:space="preserve">
|
<data name="AppExtension" xml:space="preserve">
|
||||||
<value>App Extension</value>
|
<value>App extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillAccessibilityDescription" xml:space="preserve">
|
<data name="AutofillAccessibilityDescription" xml:space="preserve">
|
||||||
<value>Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</value>
|
<value>Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</value>
|
||||||
@ -520,7 +520,7 @@
|
|||||||
<value>Get your master password hint</value>
|
<value>Get your master password hint</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImportItems" xml:space="preserve">
|
<data name="ImportItems" xml:space="preserve">
|
||||||
<value>Import Items</value>
|
<value>Import items</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImportItemsConfirmation" xml:space="preserve">
|
<data name="ImportItemsConfirmation" xml:space="preserve">
|
||||||
<value>You can bulk import items from the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>You can bulk import items from the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
@ -553,10 +553,10 @@
|
|||||||
<value>Immediately</value>
|
<value>Immediately</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeout" xml:space="preserve">
|
<data name="VaultTimeout" xml:space="preserve">
|
||||||
<value>Vault Timeout</value>
|
<value>Vault timeout</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeoutAction" xml:space="preserve">
|
<data name="VaultTimeoutAction" xml:space="preserve">
|
||||||
<value>Vault Timeout Action</value>
|
<value>Vault timeout action</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
|
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
|
||||||
<value>Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?</value>
|
<value>Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?</value>
|
||||||
@ -651,7 +651,7 @@
|
|||||||
<comment>Push notifications for apple products</comment>
|
<comment>Push notifications for apple products</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="RateTheApp" xml:space="preserve">
|
<data name="RateTheApp" xml:space="preserve">
|
||||||
<value>Rate the App</value>
|
<value>Rate the app</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RateTheAppDescription" xml:space="preserve">
|
<data name="RateTheAppDescription" xml:space="preserve">
|
||||||
<value>Please consider helping us out with a good review!</value>
|
<value>Please consider helping us out with a good review!</value>
|
||||||
@ -705,7 +705,7 @@
|
|||||||
<comment>What Apple calls their fingerprint reader.</comment>
|
<comment>What Apple calls their fingerprint reader.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoStepLogin" xml:space="preserve">
|
<data name="TwoStepLogin" xml:space="preserve">
|
||||||
<value>Two-step Login</value>
|
<value>Two-step login</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
||||||
<value>Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
@ -714,7 +714,7 @@
|
|||||||
<value>Unlock with {0}</value>
|
<value>Unlock with {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockWithPIN" xml:space="preserve">
|
<data name="UnlockWithPIN" xml:space="preserve">
|
||||||
<value>Unlock with PIN Code</value>
|
<value>Unlock with PIN code</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Validating" xml:space="preserve">
|
<data name="Validating" xml:space="preserve">
|
||||||
<value>Validating</value>
|
<value>Validating</value>
|
||||||
@ -909,11 +909,11 @@ Scanning will happen automatically.</value>
|
|||||||
<data name="CopyTotp" xml:space="preserve">
|
<data name="CopyTotp" xml:space="preserve">
|
||||||
<value>Copy TOTP</value>
|
<value>Copy TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
|
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
|
||||||
<value>If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login.</value>
|
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableAutoTotpCopy" xml:space="preserve">
|
<data name="CopyTotpAutomatically" xml:space="preserve">
|
||||||
<value>Disable Automatic TOTP Copy</value>
|
<value>Copy TOTP automatically</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumRequired" xml:space="preserve">
|
<data name="PremiumRequired" xml:space="preserve">
|
||||||
<value>A premium membership is required to use this feature.</value>
|
<value>A premium membership is required to use this feature.</value>
|
||||||
@ -1130,11 +1130,11 @@ Scanning will happen automatically.</value>
|
|||||||
<data name="Expiration" xml:space="preserve">
|
<data name="Expiration" xml:space="preserve">
|
||||||
<value>Expiration</value>
|
<value>Expiration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableWebsiteIcons" xml:space="preserve">
|
<data name="ShowWebsiteIcons" xml:space="preserve">
|
||||||
<value>Disable Website Icons</value>
|
<value>Show website icons</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
|
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
|
||||||
<value>Website Icons provide a recognizable image next to each login item in your vault.</value>
|
<value>Show a recognizable image next to each login.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="IconsUrl" xml:space="preserve">
|
<data name="IconsUrl" xml:space="preserve">
|
||||||
<value>Icons Server URL</value>
|
<value>Icons Server URL</value>
|
||||||
@ -1313,7 +1313,7 @@ Scanning will happen automatically.</value>
|
|||||||
<value>5. Select "Bitwarden"</value>
|
<value>5. Select "Bitwarden"</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordAutofill" xml:space="preserve">
|
<data name="PasswordAutofill" xml:space="preserve">
|
||||||
<value>Password AutoFill</value>
|
<value>Password auto-fill</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BitwardenAutofillAlert2" xml:space="preserve">
|
<data name="BitwardenAutofillAlert2" xml:space="preserve">
|
||||||
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
|
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
|
||||||
@ -1451,7 +1451,7 @@ Scanning will happen automatically.</value>
|
|||||||
<value>There are no folders to list.</value>
|
<value>There are no folders to list.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FingerprintPhrase" xml:space="preserve">
|
<data name="FingerprintPhrase" xml:space="preserve">
|
||||||
<value>Fingerprint Phrase</value>
|
<value>Fingerprint phrase</value>
|
||||||
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
|
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="YourAccountsFingerprint" xml:space="preserve">
|
<data name="YourAccountsFingerprint" xml:space="preserve">
|
||||||
@ -1462,10 +1462,10 @@ Scanning will happen automatically.</value>
|
|||||||
<value>Bitwarden allows you to share your vault items with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
|
<value>Bitwarden allows you to share your vault items with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExportVault" xml:space="preserve">
|
<data name="ExportVault" xml:space="preserve">
|
||||||
<value>Export Vault</value>
|
<value>Export vault</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LockNow" xml:space="preserve">
|
<data name="LockNow" xml:space="preserve">
|
||||||
<value>Lock Now</value>
|
<value>Lock now</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PIN" xml:space="preserve">
|
<data name="PIN" xml:space="preserve">
|
||||||
<value>PIN</value>
|
<value>PIN</value>
|
||||||
@ -1519,7 +1519,7 @@ Scanning will happen automatically.</value>
|
|||||||
<value>2 minutes</value>
|
<value>2 minutes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearClipboard" xml:space="preserve">
|
<data name="ClearClipboard" xml:space="preserve">
|
||||||
<value>Clear Clipboard</value>
|
<value>Clear clipboard</value>
|
||||||
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearClipboardDescription" xml:space="preserve">
|
<data name="ClearClipboardDescription" xml:space="preserve">
|
||||||
@ -1527,7 +1527,7 @@ Scanning will happen automatically.</value>
|
|||||||
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="DefaultUriMatchDetection" xml:space="preserve">
|
<data name="DefaultUriMatchDetection" xml:space="preserve">
|
||||||
<value>Default URI Match Detection</value>
|
<value>Default URI match detection</value>
|
||||||
<comment>Default URI match detection for auto-fill.</comment>
|
<comment>Default URI match detection for auto-fill.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="DefaultUriMatchDetectionDescription" xml:space="preserve">
|
<data name="DefaultUriMatchDetectionDescription" xml:space="preserve">
|
||||||
@ -1575,14 +1575,14 @@ Scanning will happen automatically.</value>
|
|||||||
<data name="BlacklistedUrisDescription" xml:space="preserve">
|
<data name="BlacklistedUrisDescription" xml:space="preserve">
|
||||||
<value>URIs that are blacklisted will not offer auto-fill. The list should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
|
<value>URIs that are blacklisted will not offer auto-fill. The list should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableSavePrompt" xml:space="preserve">
|
<data name="AskToAddLogin" xml:space="preserve">
|
||||||
<value>Disable Save Prompt</value>
|
<value>Ask to add login</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisableSavePromptDescription" xml:space="preserve">
|
<data name="AskToAddLoginDescription" xml:space="preserve">
|
||||||
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
|
<value>Ask to add an item if one isn't found in your vault.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OnRestart" xml:space="preserve">
|
<data name="OnRestart" xml:space="preserve">
|
||||||
<value>On App Restart</value>
|
<value>On app restart</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
||||||
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
|
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
|
||||||
@ -2161,13 +2161,13 @@ Scanning will happen automatically.</value>
|
|||||||
<value>Account removed successfully</value>
|
<value>Account removed successfully</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteAccount" xml:space="preserve">
|
<data name="DeleteAccount" xml:space="preserve">
|
||||||
<value>Delete Account</value>
|
<value>Delete account</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
|
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
|
||||||
<value>Deleting your account is permanent</value>
|
<value>Deleting your account is permanent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteAccountExplanation" xml:space="preserve">
|
<data name="DeleteAccountExplanation" xml:space="preserve">
|
||||||
<value>Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue?</value>
|
<value>Your account and all vault data will be erased and unrecoverable. Are you sure you want to continue?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeletingYourAccount" xml:space="preserve">
|
<data name="DeletingYourAccount" xml:space="preserve">
|
||||||
<value>Deleting your account</value>
|
<value>Deleting your account</value>
|
||||||
@ -2299,4 +2299,16 @@ select Add TOTP to store the key safely</value>
|
|||||||
<data name="AuthenticationCodesListIsVisibleActivateToShowCipherList" xml:space="preserve">
|
<data name="AuthenticationCodesListIsVisibleActivateToShowCipherList" xml:space="preserve">
|
||||||
<value>Authentication codes list is visible, activate to show cipher list.</value>
|
<value>Authentication codes list is visible, activate to show cipher list.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvironmentPageUrlsError" xml:space="preserve">
|
||||||
|
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="GenericErrorMessage" xml:space="preserve">
|
||||||
|
<value>We were unable to process your request. Please try again or contact us.</value>
|
||||||
|
</data>
|
||||||
|
<data name="AllowScreenCapture" xml:space="preserve">
|
||||||
|
<value>Allow Screen Capture</value>
|
||||||
|
</data>
|
||||||
|
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||||
|
<value>Are you sure you want to enable Screen Capture?</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
@ -19,6 +19,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
Func<AppOptions> _getOptionsFunc;
|
Func<AppOptions> _getOptionsFunc;
|
||||||
private IAccountsManagerHost _accountsManagerHost;
|
private IAccountsManagerHost _accountsManagerHost;
|
||||||
@ -28,7 +29,8 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
IStorageService secureStorageService,
|
IStorageService secureStorageService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IPlatformUtilsService platformUtilsService,
|
IPlatformUtilsService platformUtilsService,
|
||||||
IAuthService authService)
|
IAuthService authService,
|
||||||
|
ILogger logger)
|
||||||
{
|
{
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
_vaultTimeoutService = vaultTimeoutService;
|
_vaultTimeoutService = vaultTimeoutService;
|
||||||
@ -36,6 +38,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_platformUtilsService = platformUtilsService;
|
_platformUtilsService = platformUtilsService;
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||||
@ -109,42 +112,49 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
|
|
||||||
private async void OnMessage(Message message)
|
private async void OnMessage(Message message)
|
||||||
{
|
{
|
||||||
switch (message.Command)
|
try
|
||||||
{
|
{
|
||||||
case AccountsManagerMessageCommands.LOCKED:
|
switch (message.Command)
|
||||||
Locked(message.Data as Tuple<string, bool>);
|
{
|
||||||
break;
|
case AccountsManagerMessageCommands.LOCKED:
|
||||||
case AccountsManagerMessageCommands.LOCK_VAULT:
|
await Device.InvokeOnMainThreadAsync(() => LockedAsync(message.Data as Tuple<string, bool>));
|
||||||
await _vaultTimeoutService.LockAsync(true);
|
break;
|
||||||
break;
|
case AccountsManagerMessageCommands.LOCK_VAULT:
|
||||||
case AccountsManagerMessageCommands.LOGOUT:
|
await _vaultTimeoutService.LockAsync(true);
|
||||||
LogOut(message.Data as Tuple<string, bool, bool>);
|
break;
|
||||||
break;
|
case AccountsManagerMessageCommands.LOGOUT:
|
||||||
case AccountsManagerMessageCommands.LOGGED_OUT:
|
var extras = message.Data as Tuple<string, bool, bool>;
|
||||||
// Clean up old migrated key if they ever log out.
|
var userId = extras?.Item1;
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
break;
|
var expired = extras?.Item3 ?? false;
|
||||||
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(userId, userInitiated, expired));
|
||||||
AddAccount();
|
break;
|
||||||
break;
|
case AccountsManagerMessageCommands.LOGGED_OUT:
|
||||||
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
// Clean up old migrated key if they ever log out.
|
||||||
await _accountsManagerHost.UpdateThemeAsync();
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
|
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
||||||
await SwitchedAccountAsync();
|
await AddAccountAsync();
|
||||||
break;
|
break;
|
||||||
|
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
||||||
|
await _accountsManagerHost.UpdateThemeAsync();
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
|
||||||
|
await SwitchedAccountAsync();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Locked(Tuple<string, bool> extras)
|
private async Task LockedAsync(Tuple<string, bool> extras)
|
||||||
{
|
{
|
||||||
var userId = extras?.Item1;
|
var userId = extras?.Item1;
|
||||||
var userInitiated = extras?.Item2 ?? false;
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool userInitiated)
|
|
||||||
{
|
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||||
@ -163,27 +173,19 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
|
|
||||||
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
||||||
|
|
||||||
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
await Device.InvokeOnMainThreadAsync(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddAccount()
|
private async Task AddAccountAsync()
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
await Device.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
Options.HideAccountSwitcher = false;
|
Options.HideAccountSwitcher = false;
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogOut(Tuple<string, bool, bool> extras)
|
public async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
||||||
{
|
|
||||||
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 AppHelpers.LogOutAsync(userId, userInitiated);
|
||||||
await NavigateOnAccountChangeAsync();
|
await NavigateOnAccountChangeAsync();
|
||||||
|
@ -148,6 +148,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||||
|
Task<bool> GetScreenCaptureAllowedAsync(string userId = null);
|
||||||
|
Task SetScreenCaptureAllowedAsync(bool value, string userId = null);
|
||||||
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,5 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
Task<string> GetCodeAsync(string key);
|
Task<string> GetCodeAsync(string key);
|
||||||
int GetTimeInterval(string key);
|
int GetTimeInterval(string key);
|
||||||
Task<bool> IsAutoCopyEnabledAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,11 +94,13 @@ namespace Bit.Core.Models.Domain
|
|||||||
EnvironmentUrls = copy.EnvironmentUrls;
|
EnvironmentUrls = copy.EnvironmentUrls;
|
||||||
VaultTimeout = copy.VaultTimeout;
|
VaultTimeout = copy.VaultTimeout;
|
||||||
VaultTimeoutAction = copy.VaultTimeoutAction;
|
VaultTimeoutAction = copy.VaultTimeoutAction;
|
||||||
|
ScreenCaptureAllowed = copy.ScreenCaptureAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EnvironmentUrlData EnvironmentUrls;
|
public EnvironmentUrlData EnvironmentUrls;
|
||||||
public int? VaultTimeout;
|
public int? VaultTimeout;
|
||||||
public VaultTimeoutAction? VaultTimeoutAction;
|
public VaultTimeoutAction? VaultTimeoutAction;
|
||||||
|
public bool ScreenCaptureAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountVolatileData
|
public class AccountVolatileData
|
||||||
|
@ -589,7 +589,19 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
requestMessage.Version = new Version(1, 0);
|
requestMessage.Version = new Version(1, 0);
|
||||||
requestMessage.Method = method;
|
requestMessage.Method = method;
|
||||||
|
|
||||||
|
if (!Uri.IsWellFormedUriString(ApiBaseUrl, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
throw new ApiException(new ErrorResponse
|
||||||
|
{
|
||||||
|
StatusCode = HttpStatusCode.BadGateway,
|
||||||
|
//Note: This message is hardcoded until AppResources.resx gets moved into Core.csproj
|
||||||
|
Message = "One or more URLs saved in the Settings are incorrect. Please revise it and try to log in again."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
requestMessage.RequestUri = new Uri(string.Concat(ApiBaseUrl, path));
|
requestMessage.RequestUri = new Uri(string.Concat(ApiBaseUrl, path));
|
||||||
|
|
||||||
if (body != null)
|
if (body != null)
|
||||||
{
|
{
|
||||||
var bodyType = body.GetType();
|
var bodyType = body.GetType();
|
||||||
|
@ -6,6 +6,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -173,7 +174,7 @@ namespace Bit.Core.Services
|
|||||||
public void LogOut(Action callback)
|
public void LogOut(Action callback)
|
||||||
{
|
{
|
||||||
callback.Invoke();
|
callback.Invoke();
|
||||||
_messagingService.Send("loggedOut");
|
_messagingService.Send(AccountsManagerMessageCommands.LOGGED_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
|
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
|
||||||
|
@ -191,7 +191,7 @@ namespace Bit.Core.Services
|
|||||||
EnvironmentUrls = environmentUrls,
|
EnvironmentUrls = environmentUrls,
|
||||||
VaultTimeout = vaultTimeout,
|
VaultTimeout = vaultTimeout,
|
||||||
VaultTimeoutAction =
|
VaultTimeoutAction =
|
||||||
vaultTimeoutAction == "logout" ? VaultTimeoutAction.Logout : VaultTimeoutAction.Lock,
|
vaultTimeoutAction == "logout" ? VaultTimeoutAction.Logout : VaultTimeoutAction.Lock
|
||||||
};
|
};
|
||||||
var state = new State { Accounts = new Dictionary<string, Account> { [userId] = account } };
|
var state = new State { Accounts = new Dictionary<string, Account> { [userId] = account } };
|
||||||
state.ActiveUserId = userId;
|
state.ActiveUserId = userId;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@ -558,6 +559,27 @@ namespace Bit.Core.Services
|
|||||||
await SaveAccountAsync(account, reconciledOptions);
|
await SaveAccountAsync(account, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetScreenCaptureAllowedAsync(string userId = null)
|
||||||
|
{
|
||||||
|
if (CoreHelpers.ForceScreenCaptureEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await GetAccountAsync(
|
||||||
|
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
|
||||||
|
))?.Settings?.ScreenCaptureAllowed ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetScreenCaptureAllowedAsync(bool value, string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
var account = await GetAccountAsync(reconciledOptions);
|
||||||
|
account.Settings.ScreenCaptureAllowed = value;
|
||||||
|
await SaveAccountAsync(account, reconciledOptions);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<DateTime?> GetLastFileCacheClearAsync()
|
public async Task<DateTime?> GetLastFileCacheClearAsync()
|
||||||
{
|
{
|
||||||
var options = await GetDefaultStorageOptionsAsync();
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
@ -1461,6 +1483,7 @@ namespace Bit.Core.Services
|
|||||||
var existingAccount = state.Accounts[account.Profile.UserId];
|
var existingAccount = state.Accounts[account.Profile.UserId];
|
||||||
account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout;
|
account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout;
|
||||||
account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction;
|
account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction;
|
||||||
|
account.Settings.ScreenCaptureAllowed = existingAccount.Settings.ScreenCaptureAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New account defaults
|
// New account defaults
|
||||||
|
@ -10,14 +10,11 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
||||||
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
|
||||||
public TotpService(
|
public TotpService(
|
||||||
IStateService stateService,
|
|
||||||
ICryptoFunctionService cryptoFunctionService)
|
ICryptoFunctionService cryptoFunctionService)
|
||||||
{
|
{
|
||||||
_stateService = stateService;
|
|
||||||
_cryptoFunctionService = cryptoFunctionService;
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +129,5 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsAutoCopyEnabledAsync()
|
|
||||||
{
|
|
||||||
var disabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
|
||||||
return !disabled.GetValueOrDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,25 @@ namespace Bit.Core.Utilities
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether to force enabling the screen capture.
|
||||||
|
/// On Debug it will allow screen capture by default but this method
|
||||||
|
/// makes it easier to test the change on enabling/disabling the feature
|
||||||
|
/// on debug.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// To test enabling/disabling in DEBUG, just return <c>false</c> in the #if condition
|
||||||
|
/// and that's it.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool ForceScreenCaptureEnabled()
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetHostname(string uriString)
|
public static string GetHostname(string uriString)
|
||||||
{
|
{
|
||||||
var uri = GetUri(uriString);
|
var uri = GetUri(uriString);
|
||||||
|
@ -74,7 +74,7 @@ namespace Bit.Core.Utilities
|
|||||||
});
|
});
|
||||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService,
|
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService,
|
||||||
cryptoFunctionService, policyService);
|
cryptoFunctionService, policyService);
|
||||||
var totpService = new TotpService(stateService, cryptoFunctionService);
|
var totpService = new TotpService(cryptoFunctionService);
|
||||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||||
keyConnectorService);
|
keyConnectorService);
|
||||||
|
@ -8,10 +8,12 @@ using Bit.App.Utilities;
|
|||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Autofill.Models;
|
using Bit.iOS.Autofill.Models;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
|
using CoreFoundation;
|
||||||
using CoreNFC;
|
using CoreNFC;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
@ -36,88 +38,128 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public override void ViewDidLoad()
|
public override void ViewDidLoad()
|
||||||
{
|
{
|
||||||
InitApp();
|
try
|
||||||
base.ViewDidLoad();
|
|
||||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
|
||||||
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
|
||||||
_context = new Context
|
|
||||||
{
|
{
|
||||||
ExtContext = ExtensionContext
|
InitApp();
|
||||||
};
|
base.ViewDidLoad();
|
||||||
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||||
|
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
||||||
|
_context = new Context
|
||||||
|
{
|
||||||
|
ExtContext = ExtensionContext
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
|
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
try
|
||||||
_context.ServiceIdentifiers = serviceIdentifiers;
|
|
||||||
if (serviceIdentifiers.Length > 0)
|
|
||||||
{
|
{
|
||||||
var uri = serviceIdentifiers[0].Identifier;
|
InitAppIfNeeded();
|
||||||
if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||||
|
if (serviceIdentifiers.Length > 0)
|
||||||
{
|
{
|
||||||
uri = string.Concat("https://", uri);
|
var uri = serviceIdentifiers[0].Identifier;
|
||||||
|
if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
||||||
|
{
|
||||||
|
uri = string.Concat("https://", uri);
|
||||||
|
}
|
||||||
|
_context.UrlString = uri;
|
||||||
}
|
}
|
||||||
_context.UrlString = uri;
|
if (!await IsAuthed())
|
||||||
}
|
|
||||||
if (!await IsAuthed())
|
|
||||||
{
|
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
|
||||||
}
|
|
||||||
else if (await IsLocked())
|
|
||||||
{
|
|
||||||
PerformSegue("lockPasswordSegue", this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
|
||||||
{
|
{
|
||||||
PerformSegue("loginSearchSegue", this);
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
|
}
|
||||||
|
else if (await IsLocked())
|
||||||
|
{
|
||||||
|
PerformSegue("lockPasswordSegue", this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PerformSegue("loginListSegue", this);
|
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||||
|
{
|
||||||
|
PerformSegue("loginSearchSegue", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PerformSegue("loginListSegue", this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
try
|
||||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
|
||||||
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
|
||||||
if (!await IsAuthed() || await IsLocked())
|
|
||||||
{
|
{
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
InitAppIfNeeded();
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||||
ExtensionContext.CancelRequest(err);
|
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||||
return;
|
if (!await IsAuthed() || await IsLocked())
|
||||||
|
{
|
||||||
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||||
|
ExtensionContext.CancelRequest(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
|
await ProvideCredentialAsync(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
|
||||||
await ProvideCredentialAsync(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
try
|
||||||
if (!await IsAuthed())
|
|
||||||
{
|
{
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
InitAppIfNeeded();
|
||||||
return;
|
if (!await IsAuthed())
|
||||||
|
{
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
|
await CheckLockAsync(async () => await ProvideCredentialAsync());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
|
||||||
CheckLock(async () => await ProvideCredentialAsync());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareInterfaceForExtensionConfiguration()
|
public override async void PrepareInterfaceForExtensionConfiguration()
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
try
|
||||||
_context.Configuring = true;
|
|
||||||
if (!await IsAuthed())
|
|
||||||
{
|
{
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
InitAppIfNeeded();
|
||||||
return;
|
_context.Configuring = true;
|
||||||
|
if (!await IsAuthed())
|
||||||
|
{
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await CheckLockAsync(() => PerformSegue("setupSegue", this));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
CheckLock(() => PerformSegue("setupSegue", this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompleteRequest(string id = null, string username = null,
|
public void CompleteRequest(string id = null, string username = null,
|
||||||
@ -159,34 +201,43 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
||||||
{
|
{
|
||||||
if (segue.DestinationViewController is UINavigationController navController)
|
try
|
||||||
{
|
{
|
||||||
if (navController.TopViewController is LoginListViewController listLoginController)
|
if (segue.DestinationViewController is UINavigationController navController)
|
||||||
{
|
{
|
||||||
listLoginController.Context = _context;
|
if (navController.TopViewController is LoginListViewController listLoginController)
|
||||||
listLoginController.CPViewController = this;
|
{
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
listLoginController.Context = _context;
|
||||||
new CustomPresentationControllerDelegate(listLoginController.DismissModalAction);
|
listLoginController.CPViewController = this;
|
||||||
}
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
else if (navController.TopViewController is LoginSearchViewController listSearchController)
|
new CustomPresentationControllerDelegate(listLoginController.DismissModalAction);
|
||||||
{
|
}
|
||||||
listSearchController.Context = _context;
|
else if (navController.TopViewController is LoginSearchViewController listSearchController)
|
||||||
listSearchController.CPViewController = this;
|
{
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
listSearchController.Context = _context;
|
||||||
new CustomPresentationControllerDelegate(listSearchController.DismissModalAction);
|
listSearchController.CPViewController = this;
|
||||||
}
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
else if (navController.TopViewController is LockPasswordViewController passwordViewController)
|
new CustomPresentationControllerDelegate(listSearchController.DismissModalAction);
|
||||||
{
|
}
|
||||||
passwordViewController.CPViewController = this;
|
else if (navController.TopViewController is LockPasswordViewController passwordViewController)
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
{
|
||||||
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
passwordViewController.CPViewController = this;
|
||||||
}
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
else if (navController.TopViewController is SetupViewController setupViewController)
|
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
||||||
{
|
}
|
||||||
setupViewController.CPViewController = this;
|
else if (navController.TopViewController is SetupViewController setupViewController)
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
{
|
||||||
new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
|
setupViewController.CPViewController = this;
|
||||||
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
|
new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,93 +245,109 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
DismissViewController(false, async () =>
|
DismissViewController(false, async () =>
|
||||||
{
|
{
|
||||||
if (_context.CredentialIdentity != null)
|
try
|
||||||
{
|
{
|
||||||
await ProvideCredentialAsync();
|
if (_context.CredentialIdentity != null)
|
||||||
return;
|
{
|
||||||
|
await ProvideCredentialAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_context.Configuring)
|
||||||
|
{
|
||||||
|
PerformSegue("setupSegue", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||||
|
{
|
||||||
|
PerformSegue("loginSearchSegue", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PerformSegue("loginListSegue", this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_context.Configuring)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PerformSegue("setupSegue", this);
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
return;
|
throw;
|
||||||
}
|
|
||||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
|
||||||
{
|
|
||||||
PerformSegue("loginSearchSegue", this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PerformSegue("loginListSegue", this);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProvideCredentialAsync(bool userInteraction = true)
|
private async Task ProvideCredentialAsync(bool userInteraction = true)
|
||||||
{
|
{
|
||||||
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
|
try
|
||||||
Bit.Core.Models.Domain.Cipher cipher = null;
|
|
||||||
var cancel = cipherService == null || _context.CredentialIdentity?.RecordIdentifier == null;
|
|
||||||
if (!cancel)
|
|
||||||
{
|
{
|
||||||
cipher = await cipherService.GetAsync(_context.CredentialIdentity.RecordIdentifier);
|
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
|
||||||
cancel = cipher == null || cipher.Type != Bit.Core.Enums.CipherType.Login || cipher.Login == null;
|
Bit.Core.Models.Domain.Cipher cipher = null;
|
||||||
}
|
var cancel = cipherService == null || _context.CredentialIdentity?.RecordIdentifier == null;
|
||||||
if (cancel)
|
if (!cancel)
|
||||||
{
|
{
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
cipher = await cipherService.GetAsync(_context.CredentialIdentity.RecordIdentifier);
|
||||||
Convert.ToInt32(ASExtensionErrorCode.CredentialIdentityNotFound), null);
|
cancel = cipher == null || cipher.Type != Bit.Core.Enums.CipherType.Login || cipher.Login == null;
|
||||||
ExtensionContext?.CancelRequest(err);
|
}
|
||||||
return;
|
if (cancel)
|
||||||
}
|
|
||||||
|
|
||||||
var decCipher = await cipher.DecryptAsync();
|
|
||||||
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
|
||||||
{
|
|
||||||
// Prompt for password using either the lock screen or dialog unless
|
|
||||||
// already verified the password.
|
|
||||||
if (!userInteraction)
|
|
||||||
{
|
{
|
||||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
Convert.ToInt32(ASExtensionErrorCode.CredentialIdentityNotFound), null);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
|
||||||
|
var decCipher = await cipher.DecryptAsync();
|
||||||
|
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
||||||
{
|
{
|
||||||
// Add a timeout to resolve keyboard not always showing up.
|
// Prompt for password using either the lock screen or dialog unless
|
||||||
await Task.Delay(250);
|
// already verified the password.
|
||||||
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
if (!userInteraction)
|
||||||
if (!await passwordRepromptService.ShowPasswordPromptAsync())
|
|
||||||
{
|
{
|
||||||
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
||||||
|
{
|
||||||
|
// Add a timeout to resolve keyboard not always showing up.
|
||||||
|
await Task.Delay(250);
|
||||||
|
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
if (!await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
|
{
|
||||||
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
|
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
||||||
|
ExtensionContext?.CancelRequest(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
string totpCode = null;
|
||||||
string totpCode = null;
|
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
||||||
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
if (!disableTotpCopy.GetValueOrDefault(false))
|
||||||
if (!disableTotpCopy.GetValueOrDefault(false))
|
|
||||||
{
|
|
||||||
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
|
||||||
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
|
||||||
{
|
{
|
||||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
||||||
totpCode = await totpService.GetCodeAsync(decCipher.Login.Totp);
|
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
||||||
|
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
||||||
|
{
|
||||||
|
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
totpCode = await totpService.GetCodeAsync(decCipher.Login.Totp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
|
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CheckLock(Action notLockedAction)
|
private async Task CheckLockAsync(Action notLockedAction)
|
||||||
{
|
{
|
||||||
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||||
{
|
{
|
||||||
PerformSegue("lockPasswordSegue", this);
|
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -303,15 +370,21 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
if (await IsAuthed())
|
try
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
if (await IsAuthed())
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
||||||
if (deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||||
|
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using UIKit;
|
|
||||||
using Foundation;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.App.Pages;
|
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Xamarin.Forms;
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
{
|
{
|
||||||
@ -28,6 +28,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IBiometricService _biometricService;
|
private IBiometricService _biometricService;
|
||||||
private IKeyConnectorService _keyConnectorService;
|
private IKeyConnectorService _keyConnectorService;
|
||||||
|
private IAccountsManager _accountManager;
|
||||||
private bool _isPinProtected;
|
private bool _isPinProtected;
|
||||||
private bool _isPinProtectedWithKey;
|
private bool _isPinProtectedWithKey;
|
||||||
private bool _pinLock;
|
private bool _pinLock;
|
||||||
@ -39,6 +40,10 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
protected bool autofillExtension = false;
|
protected bool autofillExtension = false;
|
||||||
|
|
||||||
|
public BaseLockPasswordViewController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public BaseLockPasswordViewController(IntPtr handle)
|
public BaseLockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{ }
|
{ }
|
||||||
@ -80,7 +85,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract UITableView TableView { get; }
|
public abstract UITableView TableView { get; }
|
||||||
|
|
||||||
public override async void ViewDidLoad()
|
public override async void ViewDidLoad()
|
||||||
{
|
{
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
@ -91,6 +96,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
_accountManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
|
||||||
// We re-use the lock screen for autofill extension to verify master password
|
// We re-use the lock screen for autofill extension to verify master password
|
||||||
// when trying to access protected items.
|
// when trying to access protected items.
|
||||||
@ -168,13 +174,12 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||||
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
TableView.EstimatedRowHeight = 70;
|
||||||
|
TableView.Source = new TableSource(this);
|
||||||
|
TableView.AllowsSelection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 70;
|
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
TableView.AllowsSelection = true;
|
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
if (_biometricLock)
|
if (_biometricLock)
|
||||||
@ -191,7 +196,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ViewDidAppear(bool animated)
|
public override void ViewDidAppear(bool animated)
|
||||||
{
|
{
|
||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
@ -262,13 +267,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
if (failed)
|
if (failed)
|
||||||
{
|
{
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
await HandleFailedCredentialsAsync();
|
||||||
if (invalidUnlockAttempts >= 5)
|
|
||||||
{
|
|
||||||
await LogOutAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InvalidValue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -303,17 +302,22 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
await HandleFailedCredentialsAsync();
|
||||||
if (invalidUnlockAttempts >= 5)
|
|
||||||
{
|
|
||||||
await LogOutAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InvalidValue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleFailedCredentialsAsync()
|
||||||
|
{
|
||||||
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
await _accountManager.LogOutAsync(await _stateService.GetActiveUserIdAsync(), false, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvalidValue();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
if (!_biometricLock || !_biometricIntegrityValid)
|
if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
@ -392,38 +396,43 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
PresentViewController(alert, true, null);
|
PresentViewController(alert, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogOutAsync()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
|
base.Dispose(disposing);
|
||||||
var authService = ServiceContainer.Resolve<IAuthService>("authService");
|
|
||||||
authService.LogOut(() =>
|
MasterPasswordCell?.Dispose();
|
||||||
{
|
MasterPasswordCell = null;
|
||||||
Cancel?.Invoke();
|
|
||||||
});
|
TableView?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TableSource : ExtendedUITableViewSource
|
public class TableSource : ExtendedUITableViewSource
|
||||||
{
|
{
|
||||||
private readonly BaseLockPasswordViewController _controller;
|
private readonly WeakReference<BaseLockPasswordViewController> _controller;
|
||||||
|
|
||||||
public TableSource(BaseLockPasswordViewController controller)
|
public TableSource(BaseLockPasswordViewController controller)
|
||||||
{
|
{
|
||||||
_controller = controller;
|
_controller = new WeakReference<BaseLockPasswordViewController>(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
|
{
|
||||||
|
return new ExtendedUITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
if (indexPath.Section == 0)
|
if (indexPath.Section == 0)
|
||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (_controller._biometricUnlockOnly)
|
if (controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return _controller.BiometricCell;
|
return controller.BiometricCell;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return _controller.MasterPasswordCell;
|
return controller.MasterPasswordCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,7 +440,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (_controller._passwordReprompt)
|
if (controller._passwordReprompt)
|
||||||
{
|
{
|
||||||
var cell = new ExtendedUITableViewCell();
|
var cell = new ExtendedUITableViewCell();
|
||||||
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||||
@ -441,9 +450,9 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
else if (!_controller._biometricUnlockOnly)
|
else if (!controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return _controller.BiometricCell;
|
return controller.BiometricCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,8 +466,13 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
{
|
{
|
||||||
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
_controller._passwordReprompt
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
||||||
|
controller._passwordReprompt
|
||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
@ -484,13 +498,18 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
|
if (!_controller.TryGetTarget(out var controller))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
tableView.DeselectRow(indexPath, true);
|
||||||
tableView.EndEditing(true);
|
tableView.EndEditing(true);
|
||||||
if (indexPath.Row == 0 &&
|
if (indexPath.Row == 0 &&
|
||||||
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
((controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||||
indexPath.Section == 1))
|
indexPath.Section == 1))
|
||||||
{
|
{
|
||||||
var task = _controller.PromptBiometricAsync();
|
var task = controller.PromptBiometricAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cell = tableView.CellAt(indexPath);
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
@ -7,7 +7,11 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
public class ExtendedUIViewController : UIViewController
|
public class ExtendedUIViewController : UIViewController
|
||||||
{
|
{
|
||||||
public Action DismissModalAction { get; set; }
|
public Action DismissModalAction { get; set; }
|
||||||
|
|
||||||
|
public ExtendedUIViewController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public ExtendedUIViewController(IntPtr handle)
|
public ExtendedUIViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@ -28,16 +32,28 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
}
|
}
|
||||||
if (NavigationController?.NavigationBar != null)
|
UpdateNavigationBarTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateNavigationBarTheme()
|
||||||
|
{
|
||||||
|
UpdateNavigationBarTheme(NavigationController?.NavigationBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateNavigationBarTheme(UINavigationBar navBar)
|
||||||
|
{
|
||||||
|
if (navBar is null)
|
||||||
{
|
{
|
||||||
NavigationController.NavigationBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
return;
|
||||||
NavigationController.NavigationBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
|
||||||
NavigationController.NavigationBar.TintColor = ThemeHelpers.NavBarTextColor;
|
|
||||||
NavigationController.NavigationBar.TitleTextAttributes = new UIStringAttributes
|
|
||||||
{
|
|
||||||
ForegroundColor = ThemeHelpers.NavBarTextColor
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
|
16
src/iOS.Core/Renderers/CollectionView/CollectionException.cs
Normal file
16
src/iOS.Core/Renderers/CollectionView/CollectionException.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
|
{
|
||||||
|
public class CollectionException : Exception
|
||||||
|
{
|
||||||
|
public CollectionException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionException(string message, Exception innerEx)
|
||||||
|
: base(message, innerEx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
using Xamarin.Forms.Platform.iOS;
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
@ -13,6 +15,11 @@ namespace Bit.iOS.Core.Renderers.CollectionView
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override UICollectionViewDelegateFlowLayout CreateDelegator()
|
||||||
|
{
|
||||||
|
return new ExtendedGroupableItemsViewDelegator<TItemsView, ExtendedGroupableItemsViewController<TItemsView>>(ItemsViewLayout, this);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -21,7 +28,17 @@ namespace Bit.iOS.Core.Renderers.CollectionView
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
||||||
{
|
{
|
||||||
throw new Exception("Error in ExtendedCollectionView, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
var colEx = new CollectionException("Error in ExtendedCollectionView -> ExtendedGroupableItemsViewController, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(colEx);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Do nothing in here, this is temporary to get more info about the crash, if the logger fails, we want to get the info
|
||||||
|
// by crashing with the original exception and not the logger one
|
||||||
|
}
|
||||||
|
throw colEx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using CoreGraphics;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
|
{
|
||||||
|
public class ExtendedGroupableItemsViewDelegator<TItemsView, TViewController> : GroupableItemsViewDelegator<TItemsView, TViewController>
|
||||||
|
where TItemsView : ExtendedCollectionView
|
||||||
|
where TViewController : GroupableItemsViewController<TItemsView>
|
||||||
|
{
|
||||||
|
public ExtendedGroupableItemsViewDelegator(ItemsViewLayout itemsViewLayout, TViewController itemsViewController)
|
||||||
|
: base(itemsViewLayout, itemsViewController)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CGSize GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
// Added this to get extra information on a crash when getting the size for an item.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return base.GetSizeForItem(collectionView, layout, indexPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ViewController?.ItemsView?.ExtraDataForLogging != null)
|
||||||
|
{
|
||||||
|
var colEx = new CollectionException("Error in ExtendedCollectionView -> ExtendedGroupableItemsViewDelegator, extra data: " + ViewController.ItemsView.ExtraDataForLogging, ex);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(colEx);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Do nothing in here, this is temporary to get more info about the crash, if the logger fails, we want to get the info
|
||||||
|
// by crashing with the original exception and not the logger one
|
||||||
|
}
|
||||||
|
throw colEx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -604,6 +604,12 @@ namespace Bit.iOS.Core.Services
|
|||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetScreenCaptureAllowedAsync()
|
||||||
|
{
|
||||||
|
// only used by Android. Not possible in iOS
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public class PickerDelegate : UIDocumentPickerDelegate
|
public class PickerDelegate : UIDocumentPickerDelegate
|
||||||
{
|
{
|
||||||
private readonly DeviceActionService _deviceActionService;
|
private readonly DeviceActionService _deviceActionService;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@ -10,9 +11,11 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
public class AccountSwitchingOverlayHelper
|
public class AccountSwitchingOverlayHelper
|
||||||
{
|
{
|
||||||
IStateService _stateService;
|
const string DEFAULT_SYSTEM_AVATAR_IMAGE = "person.2";
|
||||||
IMessagingService _messagingService;
|
|
||||||
ILogger _logger;
|
readonly IStateService _stateService;
|
||||||
|
readonly IMessagingService _messagingService;
|
||||||
|
readonly ILogger _logger;
|
||||||
|
|
||||||
public AccountSwitchingOverlayHelper()
|
public AccountSwitchingOverlayHelper()
|
||||||
{
|
{
|
||||||
@ -23,9 +26,24 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
|
|
||||||
public async Task<UIImage> CreateAvatarImageAsync()
|
public async Task<UIImage> CreateAvatarImageAsync()
|
||||||
{
|
{
|
||||||
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
try
|
||||||
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
|
{
|
||||||
return avatarUIImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
|
if (_stateService is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException(nameof(_stateService));
|
||||||
|
}
|
||||||
|
|
||||||
|
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||||
|
using (var avatarUIImage = await avatarImageSource.GetNativeImageAsync())
|
||||||
|
{
|
||||||
|
return avatarUIImage?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) ?? UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
return UIImage.GetSystemImage(DEFAULT_SYSTEM_AVATAR_IMAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
|
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Services;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Internals;
|
|
||||||
using Xamarin.Forms.Platform.iOS;
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Utilities
|
namespace Bit.iOS.Core.Utilities
|
||||||
@ -17,25 +17,29 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
|
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
if (source == null || source.IsEmpty)
|
if (source == null || source.IsEmpty)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
|
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed cause IImageSourceHandler couldn't be found"));
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float scale = (float)UIScreen.MainScreen.Scale;
|
float scale = (float)UIScreen.MainScreen.Scale;
|
||||||
|
|
||||||
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
|
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Log.Warning("Image loading", "Image load cancelled");
|
LoggerHelper.LogEvenIfCantBeResolved(new OperationCanceledException("GetNativeImageAsync was cancelled"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning("Image loading", $"Image load failed: {ex}");
|
LoggerHelper.LogEvenIfCantBeResolved(new InvalidOperationException("GetNativeImageAsync failed", ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@ -15,6 +16,7 @@ using Bit.iOS.Core.Services;
|
|||||||
using CoreNFC;
|
using CoreNFC;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Utilities
|
namespace Bit.iOS.Core.Utilities
|
||||||
{
|
{
|
||||||
@ -26,6 +28,42 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public static string AppGroupId = "group.com.8bit.bitwarden";
|
public static string AppGroupId = "group.com.8bit.bitwarden";
|
||||||
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
|
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
|
||||||
|
|
||||||
|
public static void InitApp<T>(T rootController,
|
||||||
|
string clearCipherCacheKey,
|
||||||
|
NFCNdefReaderSession nfcSession,
|
||||||
|
out NFCReaderDelegate nfcDelegate,
|
||||||
|
out IAccountsManager accountsManager)
|
||||||
|
where T : UIViewController, IAccountsManagerHost
|
||||||
|
{
|
||||||
|
Forms.Init();
|
||||||
|
|
||||||
|
if (ServiceContainer.RegisteredServices.Count > 0)
|
||||||
|
{
|
||||||
|
ServiceContainer.Reset();
|
||||||
|
}
|
||||||
|
RegisterLocalServices();
|
||||||
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
||||||
|
clearCipherCacheKey,
|
||||||
|
Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||||
|
InitLogger();
|
||||||
|
Bootstrap();
|
||||||
|
|
||||||
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
|
var app = new App.App(appOptions);
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
|
||||||
|
AppearanceAdjustments();
|
||||||
|
|
||||||
|
nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||||
|
messagingService.Send("gotYubiKeyOTP", message));
|
||||||
|
SubscribeBroadcastReceiver(rootController, nfcSession, nfcDelegate);
|
||||||
|
|
||||||
|
accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
accountsManager.Init(() => appOptions, rootController);
|
||||||
|
}
|
||||||
|
|
||||||
public static void InitLogger()
|
public static void InitLogger()
|
||||||
{
|
{
|
||||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
@ -89,6 +127,7 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
|
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
public static void Bootstrap(Func<Task> postBootstrapFunc = null)
|
||||||
@ -181,7 +220,8 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"));
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
|
ServiceContainer.Resolve<ILogger>("logger"));
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
|
|
||||||
if (postBootstrapFunc != null)
|
if (postBootstrapFunc != null)
|
||||||
|
@ -201,6 +201,8 @@
|
|||||||
<Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" />
|
<Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" />
|
||||||
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" />
|
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" />
|
||||||
<Compile Include="Utilities\UISearchBarExtensions.cs" />
|
<Compile Include="Utilities\UISearchBarExtensions.cs" />
|
||||||
|
<Compile Include="Renderers\CollectionView\CollectionException.cs" />
|
||||||
|
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewDelegator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\App\App.csproj">
|
<ProjectReference Include="..\App\App.csproj">
|
||||||
|
27
src/iOS.ShareExtension/ExtensionNavigationController.cs
Normal file
27
src/iOS.ShareExtension/ExtensionNavigationController.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// This file has been autogenerated from a class added in the UI designer.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.ShareExtension
|
||||||
|
{
|
||||||
|
public partial class ExtensionNavigationController : UINavigationController
|
||||||
|
{
|
||||||
|
public ExtensionNavigationController (IntPtr handle) : base (handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UIViewController PopViewController(bool animated)
|
||||||
|
{
|
||||||
|
TopViewController?.Dispose();
|
||||||
|
return base.PopViewController(animated);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DismissModalViewController(bool animated)
|
||||||
|
{
|
||||||
|
ModalViewController?.Dispose();
|
||||||
|
base.DismissModalViewController(animated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/iOS.ShareExtension/ExtensionNavigationController.designer.cs
generated
Normal file
20
src/iOS.ShareExtension/ExtensionNavigationController.designer.cs
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// WARNING
|
||||||
|
//
|
||||||
|
// This file has been generated automatically by Visual Studio to store outlets and
|
||||||
|
// actions made in the UI designer. If it is removed, they will be lost.
|
||||||
|
// Manual changes to this file may not be handled correctly.
|
||||||
|
//
|
||||||
|
using Foundation;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
|
|
||||||
|
namespace Bit.iOS.ShareExtension
|
||||||
|
{
|
||||||
|
[Register ("ExtensionNavigationController")]
|
||||||
|
partial class ExtensionNavigationController
|
||||||
|
{
|
||||||
|
|
||||||
|
void ReleaseDesignerOutlets ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,11 @@ using Bit.App.Abstractions;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Core;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
using Bit.iOS.Core.Controllers;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
@ -24,16 +24,33 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.iOS.ShareExtension
|
namespace Bit.iOS.ShareExtension
|
||||||
{
|
{
|
||||||
public partial class LoadingViewController : ExtendedUIViewController
|
public partial class LoadingViewController : ExtendedUIViewController, IAccountsManagerHost
|
||||||
{
|
{
|
||||||
|
const string STORYBOARD_NAME = "MainInterface";
|
||||||
|
|
||||||
private Context _context = new Context();
|
private Context _context = new Context();
|
||||||
private NFCNdefReaderSession _nfcSession = null;
|
private NFCNdefReaderSession _nfcSession = null;
|
||||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||||
|
private IAccountsManager _accountsManager;
|
||||||
|
|
||||||
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||||
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>("deviceActionService");
|
|
||||||
readonly LazyResolve<IEventService> _eventService = new LazyResolve<IEventService>("eventService");
|
Lazy<UIStoryboard> _storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
|
||||||
|
|
||||||
|
private App.App _app = null;
|
||||||
|
private UIViewController _currentModalController;
|
||||||
|
private bool _presentingOnNavigationPage;
|
||||||
|
|
||||||
|
private ExtensionNavigationController ExtNavigationController
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
NavigationController.PresentationController.Delegate =
|
||||||
|
new CustomPresentationControllerDelegate(CompleteRequest);
|
||||||
|
return NavigationController as ExtensionNavigationController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public LoadingViewController(IntPtr handle)
|
public LoadingViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
@ -41,39 +58,38 @@ namespace Bit.iOS.ShareExtension
|
|||||||
|
|
||||||
public override void ViewDidLoad()
|
public override void ViewDidLoad()
|
||||||
{
|
{
|
||||||
InitApp();
|
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
|
_nfcSession, out _nfcDelegate, out _accountsManager);
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||||
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
||||||
_context.ExtensionContext = ExtensionContext;
|
_context.ExtensionContext = ExtensionContext;
|
||||||
|
_context.ProviderType = GetProviderTypeFromExtensionInputItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provider <see cref="UTType"/> given the input items
|
||||||
|
/// </summary>
|
||||||
|
private string GetProviderTypeFromExtensionInputItems()
|
||||||
|
{
|
||||||
foreach (var item in ExtensionContext.InputItems)
|
foreach (var item in ExtensionContext.InputItems)
|
||||||
{
|
{
|
||||||
var processed = false;
|
|
||||||
foreach (var itemProvider in item.Attachments)
|
foreach (var itemProvider in item.Attachments)
|
||||||
{
|
{
|
||||||
if (itemProvider.HasItemConformingTo(UTType.PlainText))
|
if (itemProvider.HasItemConformingTo(UTType.PlainText))
|
||||||
{
|
{
|
||||||
_context.ProviderType = UTType.PlainText;
|
return UTType.PlainText;
|
||||||
|
|
||||||
processed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else if (itemProvider.HasItemConformingTo(UTType.Data))
|
|
||||||
|
if (itemProvider.HasItemConformingTo(UTType.Data))
|
||||||
{
|
{
|
||||||
_context.ProviderType = UTType.Data;
|
return UTType.Data;
|
||||||
|
|
||||||
processed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (processed)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ViewDidAppear(bool animated)
|
public override async void ViewDidAppear(bool animated)
|
||||||
@ -84,12 +100,12 @@ namespace Bit.iOS.ShareExtension
|
|||||||
{
|
{
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchHomePage();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (await IsLocked())
|
else if (await IsLocked())
|
||||||
{
|
{
|
||||||
PerformSegue("lockPasswordSegue", this);
|
NavigateToLockViewController();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -102,24 +118,52 @@ namespace Bit.iOS.ShareExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
void NavigateToLockViewController()
|
||||||
{
|
{
|
||||||
if (segue.DestinationViewController is UINavigationController navController
|
var viewController = _storyboard.Value.InstantiateViewController("lockVC") as LockPasswordViewController;
|
||||||
&&
|
viewController.LoadingController = this;
|
||||||
navController.TopViewController is LockPasswordViewController passwordViewController)
|
viewController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
|
||||||
|
if (_presentingOnNavigationPage)
|
||||||
{
|
{
|
||||||
passwordViewController.LoadingController = this;
|
_presentingOnNavigationPage = false;
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
DismissViewController(true, () => ExtNavigationController.PushViewController(viewController, true));
|
||||||
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExtNavigationController.PushViewController(viewController, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DismissLockAndContinue()
|
public void DismissLockAndContinue()
|
||||||
{
|
{
|
||||||
Debug.WriteLine("BW Log, Dismissing lock controller.");
|
Debug.WriteLine("BW Log, Dismissing lock controller.");
|
||||||
|
|
||||||
|
ClearBeforeNavigating();
|
||||||
|
|
||||||
DismissViewController(false, () => ContinueOnAsync().FireAndForget());
|
DismissViewController(false, () => ContinueOnAsync().FireAndForget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DismissAndLaunch(Action pageToLaunch)
|
||||||
|
{
|
||||||
|
ClearBeforeNavigating();
|
||||||
|
|
||||||
|
DismissViewController(false, pageToLaunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBeforeNavigating()
|
||||||
|
{
|
||||||
|
_currentModalController?.Dispose();
|
||||||
|
_currentModalController = null;
|
||||||
|
|
||||||
|
if (_storyboard.IsValueCreated)
|
||||||
|
{
|
||||||
|
_storyboard.Value.Dispose();
|
||||||
|
_storyboard = null;
|
||||||
|
_storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ContinueOnAsync()
|
private async Task ContinueOnAsync()
|
||||||
{
|
{
|
||||||
Tuple<SendType, string, byte[], string> createSend = null;
|
Tuple<SendType, string, byte[], string> createSend = null;
|
||||||
@ -140,20 +184,24 @@ namespace Bit.iOS.ShareExtension
|
|||||||
CreateSend = createSend,
|
CreateSend = createSend,
|
||||||
CopyInsteadOfShareAfterSaving = true
|
CopyInsteadOfShareAfterSaving = true
|
||||||
};
|
};
|
||||||
var sendAddEditPage = new SendAddEditPage(appOptions)
|
var sendPage = new SendAddOnlyPage(appOptions)
|
||||||
{
|
{
|
||||||
OnClose = () => CompleteRequest(),
|
OnClose = () => CompleteRequest(),
|
||||||
AfterSubmit = () => CompleteRequest()
|
AfterSubmit = () => CompleteRequest()
|
||||||
};
|
};
|
||||||
|
|
||||||
var app = new App.App(appOptions);
|
SetupAppAndApplyResources(sendPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(sendAddEditPage);
|
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(sendAddEditPage);
|
NavigateToPage(sendPage);
|
||||||
var sendAddEditController = navigationPage.CreateViewController();
|
}
|
||||||
sendAddEditController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(sendAddEditController, true, null);
|
private void NavigateToPage(ContentPage page)
|
||||||
|
{
|
||||||
|
var navigationPage = new NavigationPage(page);
|
||||||
|
_currentModalController = navigationPage.CreateViewController();
|
||||||
|
_currentModalController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
_presentingOnNavigationPage = true;
|
||||||
|
PresentViewController(_currentModalController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string, byte[])> LoadDataBytesAsync()
|
private async Task<(string, byte[])> LoadDataBytesAsync()
|
||||||
@ -202,31 +250,6 @@ namespace Bit.iOS.ShareExtension
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitApp()
|
|
||||||
{
|
|
||||||
// Init Xamarin Forms
|
|
||||||
Forms.Init();
|
|
||||||
|
|
||||||
if (ServiceContainer.RegisteredServices.Count > 0)
|
|
||||||
{
|
|
||||||
ServiceContainer.Reset();
|
|
||||||
}
|
|
||||||
iOSCoreHelpers.RegisterLocalServices();
|
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent,
|
|
||||||
Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
|
||||||
iOSCoreHelpers.InitLogger();
|
|
||||||
iOSCoreHelpers.Bootstrap();
|
|
||||||
|
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
|
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
|
||||||
_nfcDelegate = new NFCReaderDelegate((success, message) =>
|
|
||||||
messagingService.Send("gotYubiKeyOTP", message));
|
|
||||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<bool> IsLocked()
|
private Task<bool> IsLocked()
|
||||||
{
|
{
|
||||||
return _vaultTimeoutService.Value.IsLockedAsync();
|
return _vaultTimeoutService.Value.IsLockedAsync();
|
||||||
@ -244,7 +267,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
if (await IsAuthed())
|
if (await IsAuthed())
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||||
if (_deviceActionService.Value.SystemMajorVersion() >= 12)
|
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
}
|
}
|
||||||
@ -252,83 +275,75 @@ namespace Bit.iOS.ShareExtension
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private App.App SetupAppAndApplyResources(ContentPage page)
|
||||||
|
{
|
||||||
|
if (_app is null)
|
||||||
|
{
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
}
|
||||||
|
ThemeManager.ApplyResourcesToPage(page);
|
||||||
|
return _app;
|
||||||
|
}
|
||||||
|
|
||||||
private void LaunchHomePage()
|
private void LaunchHomePage()
|
||||||
{
|
{
|
||||||
var homePage = new HomePage();
|
var homePage = new HomePage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(homePage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(homePage);
|
|
||||||
if (homePage.BindingContext is HomeViewModel vm)
|
if (homePage.BindingContext is HomeViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||||
vm.StartRegisterAction = () => DismissViewController(false, () => LaunchRegisterFlow());
|
vm.StartRegisterAction = () => DismissAndLaunch(() => LaunchRegisterFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
vm.StartEnvironmentAction = () => DismissViewController(false, () => LaunchEnvironmentFlow());
|
vm.StartEnvironmentAction = () => DismissAndLaunch(() => LaunchEnvironmentFlow());
|
||||||
vm.CloseAction = () => CompleteRequest();
|
vm.CloseAction = () => CompleteRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(homePage);
|
NavigateToPage(homePage);
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchEnvironmentFlow()
|
private void LaunchEnvironmentFlow()
|
||||||
{
|
{
|
||||||
var environmentPage = new EnvironmentPage();
|
var environmentPage = new EnvironmentPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(environmentPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(environmentPage);
|
ThemeManager.ApplyResourcesToPage(environmentPage);
|
||||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(environmentPage);
|
NavigateToPage(environmentPage);
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchRegisterFlow()
|
private void LaunchRegisterFlow()
|
||||||
{
|
{
|
||||||
var registerPage = new RegisterPage(null);
|
var registerPage = new RegisterPage(null);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(registerPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(registerPage);
|
|
||||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(registerPage);
|
||||||
var navigationPage = new NavigationPage(registerPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchLoginFlow(string email = null)
|
private void LaunchLoginFlow(string email = null)
|
||||||
{
|
{
|
||||||
var loginPage = new LoginPage(email);
|
var loginPage = new LoginPage(email);
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(loginPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
|
||||||
if (loginPage.BindingContext is LoginPageViewModel vm)
|
if (loginPage.BindingContext is LoginPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () =>
|
||||||
|
{
|
||||||
|
DismissLockAndContinue();
|
||||||
|
};
|
||||||
vm.CloseAction = () => CompleteRequest();
|
vm.CloseAction = () => CompleteRequest();
|
||||||
}
|
}
|
||||||
|
NavigateToPage(loginPage);
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
@ -336,22 +351,16 @@ namespace Bit.iOS.ShareExtension
|
|||||||
private void LaunchLoginSsoFlow()
|
private void LaunchLoginSsoFlow()
|
||||||
{
|
{
|
||||||
var loginPage = new LoginSsoPage();
|
var loginPage = new LoginSsoPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(loginPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(loginPage);
|
|
||||||
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(loginPage);
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
|
||||||
var loginController = navigationPage.CreateViewController();
|
|
||||||
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(loginController, true, null);
|
|
||||||
|
|
||||||
LogoutIfAuthed();
|
LogoutIfAuthed();
|
||||||
}
|
}
|
||||||
@ -359,65 +368,97 @@ namespace Bit.iOS.ShareExtension
|
|||||||
private void LaunchTwoFactorFlow(bool authingWithSso)
|
private void LaunchTwoFactorFlow(bool authingWithSso)
|
||||||
{
|
{
|
||||||
var twoFactorPage = new TwoFactorPage();
|
var twoFactorPage = new TwoFactorPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(twoFactorPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(twoFactorPage);
|
|
||||||
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||||
if (authingWithSso)
|
if (authingWithSso)
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginFlow());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginFlow());
|
||||||
}
|
}
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(twoFactorPage);
|
||||||
var navigationPage = new NavigationPage(twoFactorPage);
|
|
||||||
var twoFactorController = navigationPage.CreateViewController();
|
|
||||||
twoFactorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(twoFactorController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchSetPasswordFlow()
|
private void LaunchSetPasswordFlow()
|
||||||
{
|
{
|
||||||
var setPasswordPage = new SetPasswordPage();
|
var setPasswordPage = new SetPasswordPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(setPasswordPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(setPasswordPage);
|
|
||||||
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
}
|
}
|
||||||
|
NavigateToPage(setPasswordPage);
|
||||||
var navigationPage = new NavigationPage(setPasswordPage);
|
|
||||||
var setPasswordController = navigationPage.CreateViewController();
|
|
||||||
setPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
|
||||||
PresentViewController(setPasswordController, true, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchUpdateTempPasswordFlow()
|
private void LaunchUpdateTempPasswordFlow()
|
||||||
{
|
{
|
||||||
var updateTempPasswordPage = new UpdateTempPasswordPage();
|
var updateTempPasswordPage = new UpdateTempPasswordPage();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
SetupAppAndApplyResources(updateTempPasswordPage);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
|
||||||
ThemeManager.ApplyResourcesToPage(updateTempPasswordPage);
|
|
||||||
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
|
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.UpdateTempPasswordSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
vm.LogOutAction = () => DismissViewController(false, () => LaunchHomePage());
|
vm.LogOutAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||||
|
}
|
||||||
|
NavigateToPage(updateTempPasswordPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||||
|
{
|
||||||
|
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
||||||
|
{
|
||||||
|
ExtNavigationController.PopViewController(false);
|
||||||
|
}
|
||||||
|
else if (ExtNavigationController?.ModalViewController != null)
|
||||||
|
{
|
||||||
|
ExtNavigationController.DismissModalViewController(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(updateTempPasswordPage);
|
switch (navTarget)
|
||||||
var updateTempPasswordController = navigationPage.CreateViewController();
|
{
|
||||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
case NavigationTarget.HomeLogin:
|
||||||
PresentViewController(updateTempPasswordController, true, null);
|
ExecuteLaunch(LaunchHomePage);
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Login:
|
||||||
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchLoginFlow(loginParams.Email));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchLoginFlow());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Lock:
|
||||||
|
NavigateToLockViewController();
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Home:
|
||||||
|
DismissLockAndContinue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExecuteLaunch(Action launchAction)
|
||||||
|
{
|
||||||
|
if (_presentingOnNavigationPage)
|
||||||
|
{
|
||||||
|
DismissAndLaunch(launchAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launchAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
||||||
|
public Task UpdateThemeAsync() => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.ShareExtension
|
namespace Bit.iOS.ShareExtension
|
||||||
{
|
{
|
||||||
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController
|
public partial class LockPasswordViewController : Core.Controllers.BaseLockPasswordViewController
|
||||||
{
|
{
|
||||||
|
AccountSwitchingOverlayView _accountSwitchingOverlayView;
|
||||||
|
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
|
||||||
|
|
||||||
|
public LockPasswordViewController()
|
||||||
|
{
|
||||||
|
BiometricIntegrityKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegrityKey;
|
||||||
|
DismissModalAction = Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
public LockPasswordViewController(IntPtr handle)
|
public LockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@ -17,24 +28,80 @@ namespace Bit.iOS.ShareExtension
|
|||||||
public override UINavigationItem BaseNavItem => _navItem;
|
public override UINavigationItem BaseNavItem => _navItem;
|
||||||
public override UIBarButtonItem BaseCancelButton => _cancelButton;
|
public override UIBarButtonItem BaseCancelButton => _cancelButton;
|
||||||
public override UIBarButtonItem BaseSubmitButton => _submitButton;
|
public override UIBarButtonItem BaseSubmitButton => _submitButton;
|
||||||
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
public override Action Success => () =>
|
||||||
public override Action Cancel => () => LoadingController.CompleteRequest();
|
{
|
||||||
|
LoadingController?.Navigate(Bit.Core.Enums.NavigationTarget.Home);
|
||||||
|
LoadingController = null;
|
||||||
|
};
|
||||||
|
public override Action Cancel => () =>
|
||||||
|
{
|
||||||
|
LoadingController?.CompleteRequest();
|
||||||
|
LoadingController = null;
|
||||||
|
};
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
public override UITableView TableView => _mainTableView;
|
||||||
|
|
||||||
|
public override async void ViewDidLoad()
|
||||||
{
|
{
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
_cancelButton.TintColor = ThemeHelpers.NavBarTextColor;
|
_cancelButton.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
_submitButton.TintColor = ThemeHelpers.NavBarTextColor;
|
_submitButton.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
|
|
||||||
|
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
|
||||||
|
_accountSwitchingButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
|
||||||
|
|
||||||
|
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(_overlayView);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateNavigationBarTheme()
|
||||||
|
{
|
||||||
|
UpdateNavigationBarTheme(_navBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void AccountSwitchingButton_Activated(UIBarButtonItem sender)
|
||||||
|
{
|
||||||
|
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, _overlayView);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
var task = CheckPasswordAsync();
|
CheckPasswordAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
Cancel();
|
Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (TableView != null)
|
||||||
|
{
|
||||||
|
TableView.Source?.Dispose();
|
||||||
|
}
|
||||||
|
if (_accountSwitchingButton?.Image != null)
|
||||||
|
{
|
||||||
|
var img = _accountSwitchingButton.Image;
|
||||||
|
_accountSwitchingButton.Image = null;
|
||||||
|
img.Dispose();
|
||||||
|
}
|
||||||
|
if (_accountSwitchingOverlayView != null && _overlayView?.Subviews != null)
|
||||||
|
{
|
||||||
|
foreach (var subView in _overlayView.Subviews)
|
||||||
|
{
|
||||||
|
subView.RemoveFromSuperview();
|
||||||
|
subView.Dispose();
|
||||||
|
}
|
||||||
|
_accountSwitchingOverlayView = null;
|
||||||
|
_overlayView.RemoveFromSuperview();
|
||||||
|
}
|
||||||
|
_accountSwitchingOverlayHelper = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,18 +12,30 @@ namespace Bit.iOS.ShareExtension
|
|||||||
[Register ("LockPasswordViewController")]
|
[Register ("LockPasswordViewController")]
|
||||||
partial class LockPasswordViewController
|
partial class LockPasswordViewController
|
||||||
{
|
{
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UIBarButtonItem _accountSwitchingButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UIBarButtonItem _cancelButton { get; set; }
|
UIKit.UIBarButtonItem _cancelButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UITableView _mainTableView { get; set; }
|
UIKit.UITableView _mainTableView { get; set; }
|
||||||
|
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UINavigationBar _navBar { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UINavigationItem _navItem { get; set; }
|
UIKit.UINavigationItem _navItem { get; set; }
|
||||||
|
|
||||||
|
[Outlet]
|
||||||
|
UIKit.UIView _overlayView { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
UIKit.UIBarButtonItem _submitButton { get; set; }
|
UIKit.UIBarButtonItem _submitButton { get; set; }
|
||||||
|
|
||||||
|
[Action ("AccountSwitchingButton_Activated:")]
|
||||||
|
partial void AccountSwitchingButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
|
|
||||||
[Action ("CancelButton_Activated:")]
|
[Action ("CancelButton_Activated:")]
|
||||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
|
|
||||||
@ -32,6 +44,11 @@ namespace Bit.iOS.ShareExtension
|
|||||||
|
|
||||||
void ReleaseDesignerOutlets ()
|
void ReleaseDesignerOutlets ()
|
||||||
{
|
{
|
||||||
|
if (_accountSwitchingButton != null) {
|
||||||
|
_accountSwitchingButton.Dispose ();
|
||||||
|
_accountSwitchingButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_cancelButton != null) {
|
if (_cancelButton != null) {
|
||||||
_cancelButton.Dispose ();
|
_cancelButton.Dispose ();
|
||||||
_cancelButton = null;
|
_cancelButton = null;
|
||||||
@ -47,10 +64,20 @@ namespace Bit.iOS.ShareExtension
|
|||||||
_navItem = null;
|
_navItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_overlayView != null) {
|
||||||
|
_overlayView.Dispose ();
|
||||||
|
_overlayView = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_submitButton != null) {
|
if (_submitButton != null) {
|
||||||
_submitButton.Dispose ();
|
_submitButton.Dispose ();
|
||||||
_submitButton = null;
|
_submitButton = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_navBar != null) {
|
||||||
|
_navBar.Dispose ();
|
||||||
|
_navBar = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="2vH-Do-uhk">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="2vH-Do-uhk">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@ -11,10 +13,6 @@
|
|||||||
<scene sceneID="kFr-IN-5GS">
|
<scene sceneID="kFr-IN-5GS">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="bHU-LX-EpF" customClass="LoadingViewController" sceneMemberID="viewController">
|
<viewController id="bHU-LX-EpF" customClass="LoadingViewController" sceneMemberID="viewController">
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="8LE-gl-yDT"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="MuK-nA-9iu"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="z2O-Vp-jY9">
|
<view key="view" contentMode="scaleToFill" id="z2O-Vp-jY9">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
@ -23,25 +21,25 @@
|
|||||||
<rect key="frame" x="66" y="352" width="282" height="44"/>
|
<rect key="frame" x="66" y="352" width="282" height="44"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="jNx-Vd-K6U"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerX" secondItem="z2O-Vp-jY9" secondAttribute="centerX" id="6DT-HB-vS5"/>
|
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerX" secondItem="jNx-Vd-K6U" secondAttribute="centerX" id="6DT-HB-vS5"/>
|
||||||
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerY" secondItem="z2O-Vp-jY9" secondAttribute="centerY" constant="-30" id="o9N-Tv-Iwq"/>
|
<constraint firstItem="Zdy-yw-n0p" firstAttribute="centerY" secondItem="z2O-Vp-jY9" secondAttribute="centerY" constant="-30" id="o9N-Tv-Iwq"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" id="74l-Va-Vqa"/>
|
<navigationItem key="navigationItem" id="74l-Va-Vqa"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="Logo" destination="Zdy-yw-n0p" id="1Qk-EK-0BO"/>
|
<outlet property="Logo" destination="Zdy-yw-n0p" id="1Qk-EK-0BO"/>
|
||||||
<segue destination="rh6-Mf-4Ja" kind="presentation" identifier="lockPasswordSegue" id="ZUl-jv-5se"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yJx-cc-wzs" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="yJx-cc-wzs" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-374" y="560"/>
|
<point key="canvasLocation" x="-374" y="560"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Navigation Controller-->
|
<!--Extension Navigation Controller-->
|
||||||
<scene sceneID="Wgx-vz-XqL">
|
<scene sceneID="Wgx-vz-XqL">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="2vH-Do-uhk" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="2vH-Do-uhk" customClass="ExtensionNavigationController" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" hidden="YES" contentMode="scaleToFill" translucent="NO" id="JoO-jQ-16M">
|
<navigationBar key="navigationBar" hidden="YES" contentMode="scaleToFill" translucent="NO" id="JoO-jQ-16M">
|
||||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
@ -54,60 +52,89 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-1097" y="564"/>
|
<point key="canvasLocation" x="-1097" y="564"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Navigation Controller-->
|
<!--Lock Password View Controller-->
|
||||||
<scene sceneID="Tzp-2o-9k7">
|
<scene sceneID="vQB-cT-8IC">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="rh6-Mf-4Ja" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="lockVC" id="Vi7-LV-nWW" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="UDq-kw-Ue7">
|
<view key="view" contentMode="scaleToFill" id="Vfd-7B-19G">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
|
||||||
</navigationBar>
|
|
||||||
<connections>
|
|
||||||
<segue destination="85y-W9-d8q" kind="relationship" relationship="rootViewController" id="TeA-GE-A22"/>
|
|
||||||
</connections>
|
|
||||||
</navigationController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="BVV-5B-aim" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="-375" y="1262"/>
|
|
||||||
</scene>
|
|
||||||
<!--Verify Master Password-->
|
|
||||||
<scene sceneID="OEb-ak-BVc">
|
|
||||||
<objects>
|
|
||||||
<tableViewController id="85y-W9-d8q" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="9on-wf-zdb">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<subviews>
|
||||||
<connections>
|
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" estimatedSectionHeaderHeight="-1" sectionFooterHeight="18" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="M1A-84-x5l">
|
||||||
<outlet property="dataSource" destination="85y-W9-d8q" id="3il-RO-S3K"/>
|
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||||
<outlet property="delegate" destination="85y-W9-d8q" id="bLb-h4-pr3"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</connections>
|
</tableView>
|
||||||
</tableView>
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ijE-Pa-OBq" userLabel="OverlayView">
|
||||||
<navigationItem key="navigationItem" title="Verify Master Password" id="qL3-iV-6Ld">
|
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="d8j-HZ-erD">
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<connections>
|
</view>
|
||||||
<action selector="CancelButton_Activated:" destination="85y-W9-d8q" id="p54-B0-Vyf"/>
|
<navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fav-Fz-6ZK">
|
||||||
</connections>
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
</barButtonItem>
|
<items>
|
||||||
<barButtonItem key="rightBarButtonItem" title="Submit" id="8a7-Vz-SJA">
|
<navigationItem title="Verify Master Password" id="aka-In-IYk">
|
||||||
<connections>
|
<leftBarButtonItems>
|
||||||
<action selector="SubmitButton_Activated:" destination="85y-W9-d8q" id="P8A-7O-lpY"/>
|
<barButtonItem title="Cancel" id="LrG-Qx-w4Q">
|
||||||
</connections>
|
<connections>
|
||||||
</barButtonItem>
|
<action selector="CancelButton_Activated:" destination="Vi7-LV-nWW" id="qyZ-i9-Dwz"/>
|
||||||
</navigationItem>
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
<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="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>
|
<connections>
|
||||||
<outlet property="_cancelButton" destination="d8j-HZ-erD" id="wlI-el-Snh"/>
|
<outlet property="_accountSwitchingButton" destination="nlD-Xn-HtM" id="SSG-zv-bAc"/>
|
||||||
<outlet property="_mainTableView" destination="9on-wf-zdb" id="ltj-yY-5ue"/>
|
<outlet property="_cancelButton" destination="LrG-Qx-w4Q" id="aag-ZZ-Ifs"/>
|
||||||
<outlet property="_navItem" destination="qL3-iV-6Ld" id="Grb-Ta-NCF"/>
|
<outlet property="_mainTableView" destination="M1A-84-x5l" id="pA4-ao-Fhu"/>
|
||||||
<outlet property="_submitButton" destination="8a7-Vz-SJA" id="LS8-6Y-Wkp"/>
|
<outlet property="_navBar" destination="fav-Fz-6ZK" id="Q9p-Dw-ipx"/>
|
||||||
|
<outlet property="_navItem" destination="aka-In-IYk" id="www-Lt-x1g"/>
|
||||||
|
<outlet property="_overlayView" destination="ijE-Pa-OBq" id="n9e-Lg-4WO"/>
|
||||||
|
<outlet property="_submitButton" destination="oQD-QK-YPB" id="SEp-KK-YeP"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="5by-Sa-d9m" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Czu-9n-yKC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="335" y="1260"/>
|
<point key="canvasLocation" x="403" y="560"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="logo.png" width="282" height="44"/>
|
<image name="logo.png" width="282" height="44"/>
|
||||||
|
<image name="person.2" catalog="system" width="128" height="81"/>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
<MtouchLink>None</MtouchLink>
|
<MtouchLink>None</MtouchLink>
|
||||||
<MtouchArch>x86_64</MtouchArch>
|
<MtouchArch>x86_64</MtouchArch>
|
||||||
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
|
||||||
<DeviceSpecificBuild>false</DeviceSpecificBuild>
|
|
||||||
<MtouchVerbosity></MtouchVerbosity>
|
<MtouchVerbosity></MtouchVerbosity>
|
||||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||||
<AssemblyName>BitwardeniOSShareExtension</AssemblyName>
|
<AssemblyName>BitwardeniOSShareExtension</AssemblyName>
|
||||||
@ -193,6 +192,10 @@
|
|||||||
<Compile Include="LockPasswordViewController.designer.cs">
|
<Compile Include="LockPasswordViewController.designer.cs">
|
||||||
<DependentUpon>LockPasswordViewController.cs</DependentUpon>
|
<DependentUpon>LockPasswordViewController.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="ExtensionNavigationController.cs" />
|
||||||
|
<Compile Include="ExtensionNavigationController.designer.cs">
|
||||||
|
<DependentUpon>ExtensionNavigationController.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\App\App.csproj">
|
<ProjectReference Include="..\App\App.csproj">
|
||||||
|
Loading…
Reference in New Issue
Block a user