1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-11 05:43:30 +00:00

[SSG-416] Mobile PR Fixes

This commit is contained in:
André Bispo
2022-07-12 23:00:31 +01:00
parent 8ed909eb91
commit 7cf34b845e
21 changed files with 507 additions and 404 deletions

View File

@@ -9,30 +9,23 @@
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore" xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform" StyleClass="list-row, list-row-platform"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
x:DataType="pages:GroupingsPageTOTPListItem"> x:DataType="pages:GroupingsPageTOTPListItem"
ColumnDefinitions="40,*,40,Auto,40"
RowSpacing="0"
Padding="0,10,0,0"
RowDefinitions="Auto,Auto">
<Grid.Resources> <Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter" /> <u:IconGlyphConverter x:Key="iconGlyphConverter" />
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources> </Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<controls:IconLabel <controls:IconLabel
Grid.Column="0" Grid.Column="0"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" StyleClass="list-icon, list-icon-platform"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}" Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
@@ -46,47 +39,35 @@
VerticalOptions="Center" VerticalOptions="Center"
WidthRequest="22" WidthRequest="22"
HeightRequest="22" HeightRequest="22"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage}" IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}" Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
<Grid <Label
RowSpacing="0" LineBreakMode="TailTruncation"
ColumnSpacing="0"
Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalOptions="Center" Grid.Row="0"
HorizontalOptions="FillAndExpand" VerticalTextAlignment="End"
Padding="0, 15, 15, 10"> VerticalOptions="End"
<Grid.RowDefinitions> StyleClass="list-title, list-title-platform"
<RowDefinition Height="Auto" /> Text="{Binding Cipher.Name}" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label <Label
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation"
Grid.Column="0" Grid.Column="1"
Grid.Row="0" Grid.Row="1"
StyleClass="list-title, list-title-platform" VerticalTextAlignment="Start"
Text="{Binding Cipher.Name}" /> VerticalOptions="Start"
<Label StyleClass="list-subtitle, list-subtitle-platform"
LineBreakMode="TailTruncation" Text="{Binding Cipher.SubTitle}" />
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" />
</Grid>
<controls:CircularProgressbarView <controls:CircularProgressbarView
Progress="{Binding Progress}" Progress="{Binding Progress}"
ProgressColor="{StaticResource PrimaryColor}"
EndingProgressColor="{StaticResource DangerColor}"
BackgroundProgressColor="{StaticResource BackgroundColor}"
StrokeWidth="3"
Radius="15"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
HorizontalOptions="FillAndExpand" Grid.RowSpan="2"
HorizontalOptions="Fill"
VerticalOptions="CenterAndExpand" /> VerticalOptions="CenterAndExpand" />
<Label <Label
@@ -94,20 +75,22 @@
Style="{DynamicResource textTotp}" Style="{DynamicResource textTotp}"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Grid.RowSpan="2"
StyleClass="text-sm" StyleClass="text-sm"
XAlign="Center" HorizontalTextAlignment="Center"
YAlign="Center" HorizontalOptions="Fill"
HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center"
VerticalOptions="FillAndExpand" /> VerticalOptions="Fill" />
<StackLayout <StackLayout
Grid.Row="0" Grid.Row="0"
Grid.Column="3" Grid.Column="3"
Margin="3,0,2,0" Margin="3,0,2,0"
Spacing="5" Spacing="5"
Grid.RowSpan="2"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalOptions="FillAndExpand" HorizontalOptions="Fill"
VerticalOptions="FillAndExpand"> VerticalOptions="Fill">
<controls:MonoLabel <controls:MonoLabel
Text="{Binding TotpCodeFormattedStart, Mode=OneWay}" Text="{Binding TotpCodeFormattedStart, Mode=OneWay}"
@@ -135,6 +118,7 @@
CommandParameter="LoginTotp" CommandParameter="LoginTotp"
Grid.Row="0" Grid.Row="0"
Grid.Column="4" Grid.Column="4"
Grid.RowSpan="2"
Padding="0,0,1,0" Padding="0,0,1,0"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"

View File

@@ -63,12 +63,5 @@ namespace Bit.App.Controls
} }
} }
private string _totpCodeFormatted = "938 928";
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => _totpCodeFormatted = value;
}
} }
} }

View File

@@ -0,0 +1,139 @@
using System;
using System.Runtime.CompilerServices;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class CircularProgressbarView : SKCanvasView
{
private Circle _circle;
public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
nameof(Progress), typeof(double), typeof(CircularProgressbarView), propertyChanged: OnProgressChanged);
public static readonly BindableProperty RadiusProperty = BindableProperty.Create(
nameof(Radius), typeof(float), typeof(CircularProgressbarView), 15f);
public static readonly BindableProperty StrokeWidthProperty = BindableProperty.Create(
nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public double Progress
{
get { return (double)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
public float Radius
{
get => (float)GetValue(RadiusProperty);
set => SetValue(RadiusProperty, value);
}
public float StrokeWidth
{
get => (float)GetValue(StrokeWidthProperty);
set => SetValue(StrokeWidthProperty, value);
}
public Color ProgressColor
{
get => (Color)GetValue(ProgressColorProperty);
set => SetValue(ProgressColorProperty, value);
}
public Color EndingProgressColor
{
get => (Color)GetValue(EndingProgressColorProperty);
set => SetValue(EndingProgressColorProperty, value);
}
public Color BackgroundProgressColor
{
get => (Color)GetValue(BackgroundProgressColorProperty);
set => SetValue(BackgroundProgressColorProperty, value);
}
private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var context = bindable as CircularProgressbarView;
context.InvalidateSurface();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(Progress))
{
_circle = new Circle(Radius * (float)DeviceDisplay.MainDisplayInfo.Density, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
}
}
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
base.OnPaintSurface(e);
if(_circle != null)
{
_circle.CalculateCenter(e.Info);
e.Surface.Canvas.Clear();
DrawCircle(e.Surface.Canvas, _circle, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, BackgroundProgressColor.ToSKColor());
DrawArc(e.Surface.Canvas, _circle, () => (float)Progress, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, ProgressColor.ToSKColor(), EndingProgressColor.ToSKColor());
}
}
private void DrawCircle(SKCanvas canvas, Circle circle, float strokewidth, SKColor color)
{
canvas.DrawCircle(circle.Center, circle.Redius,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = color,
IsStroke = true,
IsAntialias = true
});
}
private void DrawArc(SKCanvas canvas, Circle circle, Func<float> progress, float strokewidth, SKColor color, SKColor progressEndColor)
{
var progressValue = progress.Invoke();
var angle = progressValue * 3.6f;
canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = progressValue < 20f ? progressEndColor : color,
IsStroke = true,
IsAntialias = true
});
}
}
public class Circle
{
private readonly Func<SKImageInfo, SKPoint> _centerFunc;
public Circle(float redius, Func<SKImageInfo, SKPoint> centerFunc)
{
_centerFunc = centerFunc;
Redius = redius;
}
public SKPoint Center { get; set; }
public float Redius { get; set; }
public SKRect Rect => new SKRect(Center.X - Redius, Center.Y - Redius, Center.X + Redius, Center.Y + Redius);
public void CalculateCenter(SKImageInfo argsInfo)
{
Center = _centerFunc(argsInfo);
}
}
}

View File

@@ -1,10 +0,0 @@
<?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:forms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="Bit.App.Controls.CircularProgressbarView">
<forms:SKCanvasView x:Name="SkCanvasView"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"/>
</ContentView>

View File

@@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class CircularProgressbarView : ContentView
{
private ProgressDrawer _progressDrawer;
public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
"Progress", typeof(double), typeof(CircularProgressbarView), propertyChanged: OnProgressChanged);
public double Progress
{
get { return (double)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
public float Radius { get; set; }
public float StrokeWidth { get; set; }
public Color ProgressColor { get; set; }
public Color EndingProgressColor { get; set; }
public Color BackgroundProgressColor { get; set; }
private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var context = bindable as CircularProgressbarView;
context.SkCanvasView.InvalidateSurface();
}
public CircularProgressbarView()
{
InitializeComponent();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(Progress) && _progressDrawer == null)
{
var circle = new Circle(Radius * (float)DeviceDisplay.MainDisplayInfo.Density, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
_progressDrawer = new ProgressDrawer(SkCanvasView, circle, () => (float)Progress, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, BackgroundProgressColor.ToSKColor(), ProgressColor.ToSKColor(), EndingProgressColor.ToSKColor());
}
}
}
public class Circle
{
private readonly Func<SKImageInfo, SKPoint> _centerfunc;
public Circle(float redius, Func<SKImageInfo, SKPoint> centerfunc)
{
_centerfunc = centerfunc;
Redius = redius;
}
public SKPoint Center { get; set; }
public float Redius { get; set; }
public SKRect Rect => new SKRect(Center.X - Redius, Center.Y - Redius, Center.X + Redius, Center.Y + Redius);
public void CalculateCenter(SKImageInfo argsInfo)
{
Center = _centerfunc.Invoke(argsInfo);
}
}
public class ProgressDrawer
{
public ProgressDrawer(SKCanvasView canvas, Circle circle, Func<float> progress, float strokeWidth, SKColor progressColor, SKColor foregroundColor, SKColor progressEndColor)
{
canvas.PaintSurface += (sender, args) =>
{
circle.CalculateCenter(args.Info);
args.Surface.Canvas.Clear();
DrawCircle(args.Surface.Canvas, circle, strokeWidth, progressColor);
DrawArc(args.Surface.Canvas, circle, progress, strokeWidth, foregroundColor, progressEndColor);
};
}
private void DrawCircle(SKCanvas canvas, Circle circle, float strokewidth, SKColor color)
{
canvas.DrawCircle(circle.Center, circle.Redius,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = color,
IsStroke = true,
IsAntialias = true
});
}
private void DrawArc(SKCanvas canvas, Circle circle, Func<float> progress, float strokewidth, SKColor color, SKColor progressEndColor)
{
var progressValue = progress.Invoke();
var angle = progressValue * 3.6f;
canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = progressValue < 20f ? progressEndColor : color,
IsStroke = true,
IsAntialias = true
});
}
}
}

View File

@@ -11,6 +11,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -92,7 +93,7 @@ namespace Bit.App.Pages
UriOptionsCommand = new Command<LoginUriView>(UriOptions); UriOptionsCommand = new Command<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions); FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions);
PasswordPromptHelpCommand = new Command(PasswordPromptHelp); PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
CopyCommand = new Command(CopyTotpClipboard); CopyCommand = new AsyncCommand(CopyTotpClipboardAsync);
Uris = new ExtendedObservableCollection<LoginUriView>(); Uris = new ExtendedObservableCollection<LoginUriView>();
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>(); Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>(); Collections = new ExtendedObservableCollection<CollectionViewModel>();
@@ -154,7 +155,7 @@ namespace Bit.App.Pages
public Command UriOptionsCommand { get; set; } public Command UriOptionsCommand { get; set; }
public Command FieldOptionsCommand { get; set; } public Command FieldOptionsCommand { get; set; }
public Command PasswordPromptHelpCommand { get; set; } public Command PasswordPromptHelpCommand { get; set; }
public Command CopyCommand { get; set; } public AsyncCommand CopyCommand { get; set; }
public string CipherId { get; set; } public string CipherId { get; set; }
public string OrganizationId { get; set; } public string OrganizationId { get; set; }
public string FolderId { get; set; } public string FolderId { get; set; }
@@ -305,8 +306,8 @@ namespace Bit.App.Pages
public bool AllowPersonal { get; set; } public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None; public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => !string.IsNullOrEmpty(Cipher.Login.Totp); public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTOTP}"; public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
public void Init() public void Init()
{ {
PageTitle = EditMode && !CloneMode ? AppResources.EditItem : AppResources.AddItem; PageTitle = EditMode && !CloneMode ? AppResources.EditItem : AppResources.AddItem;
@@ -865,10 +866,17 @@ namespace Bit.App.Pages
} }
} }
private async void CopyTotpClipboard() private async Task CopyTotpClipboardAsync()
{ {
await _clipboardService.CopyTextAsync(_cipher.Login.Totp); try
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.AuthenticatorKeyScanner)); {
await _clipboardService.CopyTextAsync(_cipher.Login.Totp);
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.AuthenticatorKeyScanner));
}
catch (Exception ex)
{
_logger.Exception(ex);
}
} }
} }

View File

@@ -6,6 +6,7 @@
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects" xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore" xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:GroupingsPageViewModel" x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}" Title="{Binding PageTitle}"
@@ -143,7 +144,6 @@
<StackLayout <StackLayout
IsVisible="{Binding ShowTotpFilter}" IsVisible="{Binding ShowTotpFilter}"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
Margin="0,5,10,0"> Margin="0,5,10,0">
<Label <Label
Text="{u:I18n DisplayItemsContainingTOTP}" Text="{u:I18n DisplayItemsContainingTOTP}"
@@ -158,7 +158,14 @@
IsToggled="{Binding TotpFilterEnable}" IsToggled="{Binding TotpFilterEnable}"
StyleClass="box-value" StyleClass="box-value"
HorizontalOptions="End" HorizontalOptions="End"
Toggled="TotpFilter_Toggled" /> AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding ShowTotpCodesAccessibilityText}">
<Switch.Behaviors>
<xct:EventToCommandBehavior
EventName="Toggled"
Command="{Binding TotpFilterCommand}" />
</Switch.Behaviors>
</Switch>
</StackLayout> </StackLayout>
<StackLayout <StackLayout

View File

@@ -189,11 +189,11 @@ namespace Bit.App.Pages
return false; return false;
} }
protected override void OnDisappearing() protected override async void OnDisappearing()
{ {
base.OnDisappearing(); base.OnDisappearing();
IsBusy = false; IsBusy = false;
_vm.TotpFilterEnable = false; await _vm.StopCiphersTotpTick();
_broadcasterService.Unsubscribe(_pageName); _broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing(); _vm.DisableRefreshing();
_accountAvatar?.OnDisappearing(); _accountAvatar?.OnDisappearing();
@@ -305,10 +305,5 @@ namespace Bit.App.Pages
{ {
await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
} }
void TotpFilter_Toggled(System.Object sender, Xamarin.Forms.ToggledEventArgs e)
{
_vm.TotpFilterCommand.Execute(null);
}
} }
} }

View File

@@ -12,6 +12,7 @@ namespace Bit.App.Pages
{ {
public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem
{ {
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private readonly ITotpService _totpService; private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
@@ -23,8 +24,8 @@ namespace Bit.App.Pages
public int interval { get; set; } public int interval { get; set; }
private double _progress; private double _progress;
private string _totpSec; private string _totpSec;
private string _totpCode; private string _totpCodeFormatted;
private string _totpCodeFormatted = "938 928"; private TotpHelper _totpTickHelper;
public GroupingsPageTOTPListItem(CipherView cipherView, bool websiteIconsEnabled) public GroupingsPageTOTPListItem(CipherView cipherView, bool websiteIconsEnabled)
@@ -39,10 +40,9 @@ namespace Bit.App.Pages
CopyCommand = new AsyncCommand(CopyToClipboardAsync, CopyCommand = new AsyncCommand(CopyToClipboardAsync,
onException: ex => _logger.Value.Exception(ex), onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
_totpTickHelper = new TotpHelper(cipherView);
} }
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public AsyncCommand CopyCommand { get; set; } public AsyncCommand CopyCommand { get; set; }
public CipherView Cipher public CipherView Cipher
@@ -110,39 +110,10 @@ namespace Bit.App.Pages
public async Task TotpTickAsync() public async Task TotpTickAsync()
{ {
var epoc = CoreHelpers.EpocUtcNow() / 1000; await _totpTickHelper.GenerateNewTotpValues();
var mod = epoc % interval; TotpSec = _totpTickHelper.TotpSec;
var totpSec = interval - mod; Progress = _totpTickHelper.Progress;
TotpSec = totpSec.ToString(); TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
Progress = totpSec * 100 / 30;
//TotpLow = totpSec < 7;
if (mod == 0)
{
await TotpUpdateCodeAsync();
}
}
public async Task TotpUpdateCodeAsync()
{
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
{
if (_totpCode.Length > 4)
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
@@ -38,7 +39,8 @@ namespace Bit.App.Pages
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>(); private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
private Dictionary<CipherType, int> _typeCounts = new Dictionary<CipherType, int>(); private Dictionary<CipherType, int> _typeCounts = new Dictionary<CipherType, int>();
private int _deletedCount = 0; private int _deletedCount = 0;
private CancellationTokenSource _totpTickCancellationToken;
private Task _totpTickTask;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
@@ -99,6 +101,9 @@ namespace Bit.App.Pages
public bool HasCiphers { get; set; } public bool HasCiphers { get; set; }
public bool HasFolders { get; set; } public bool HasFolders { get; set; }
public bool HasCollections { get; set; } public bool HasCollections { get; set; }
public string ShowTotpCodesAccessibilityText => TotpFilterEnable ?
AppResources.AuthenticationCodesListIsVisibleActivateToShowCipherList
: AppResources.CipherListIsVisibleActivateToShowAuthenticationCodesList;
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
&& NoFolderCiphers.Count < NoFolderListSize && NoFolderCiphers.Count < NoFolderListSize
&& (Collections is null || !Collections.Any()); && (Collections is null || !Collections.Any());
@@ -292,33 +297,7 @@ namespace Bit.App.Pages
} }
if (Ciphers?.Any() ?? false) if (Ciphers?.Any() ?? false)
{ {
if (TotpFilterEnable) CreateCipherGroupedItems(ref groupedItems);
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted && !string.IsNullOrEmpty(c.Login.Totp))
.Select(c => new GroupingsPageTOTPListItem(c, true)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
foreach (var item in ciphersListItems)
{
await item.TotpUpdateCodeAsync();
}
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
foreach (var item in ciphersListItems)
{
item.TotpTickAsync();
}
return TotpFilterEnable;
});
}
else
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
} }
if (ShowNoFolderCipherGroup) if (ShowNoFolderCipherGroup)
{ {
@@ -406,6 +385,45 @@ namespace Bit.App.Pages
} }
} }
private void CreateCipherGroupedItems(ref List<GroupingsPageListGroup> groupedItems)
{
var uppercaseGroupNames = _deviceActionService.DeviceType == DeviceType.iOS;
_totpTickCancellationToken?.Cancel();
if (TotpFilterEnable)
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted && !string.IsNullOrEmpty(c.Login.Totp))
.Select(c => new GroupingsPageTOTPListItem(c, true)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
StartCiphersTotpTick(ciphersListItems);
}
else
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
}
private void StartCiphersTotpTick(List<GroupingsPageTOTPListItem> ciphersListItems)
{
_totpTickCancellationToken?.Cancel();
_totpTickCancellationToken = new CancellationTokenSource();
_totpTickTask = new TimerTask(() => { ciphersListItems.ForEach(i => i.TotpTickAsync()); }, _totpTickCancellationToken).Run();
}
public async Task StopCiphersTotpTick()
{
TotpFilterEnable = false;
_totpTickCancellationToken?.Cancel();
if (_totpTickTask != null)
{
await _totpTickTask;
}
}
public void DisableRefreshing() public void DisableRefreshing()
{ {
Refreshing = false; Refreshing = false;

View File

@@ -70,7 +70,7 @@
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}" Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Start" VerticalOptions="Start"
FontSize="30" FontSize="Title"
TextColor="Transparent"/> TextColor="Transparent"/>
</StackLayout> </StackLayout>
<BoxView <BoxView
@@ -88,8 +88,8 @@
Grid.RowSpan="2" Grid.RowSpan="2"
Margin="30,0"> Margin="30,0">
<Label <Label
Text="{u:I18n EnterCodeManually}" Text="{u:I18n EnterKeyManually}"
FontSize="30" /> FontSize="Title" />
<Label <Label
Text="{u:I18n AuthenticatorKeyScanner}" Text="{u:I18n AuthenticatorKeyScanner}"
StyleClass="box-label" /> StyleClass="box-label" />
@@ -121,12 +121,7 @@
AutomationId="zxingDefaultOverlay_TopTextLabel" AutomationId="zxingDefaultOverlay_TopTextLabel"
Margin="30,15,30,0" Margin="30,15,30,0"
HorizontalOptions="Center" HorizontalOptions="Center"
StyleClass="text-sm" HorizontalTextAlignment="Center"
TextColor="White" />
<Label
Text="{Binding CameraInstructionBottom}"
AutomationId="zxingDefaultOverlay_BottomTextLabel"
HorizontalOptions="Center"
StyleClass="text-sm" StyleClass="text-sm"
TextColor="White" /> TextColor="White" />
</StackLayout> </StackLayout>

View File

@@ -112,33 +112,44 @@ namespace Bit.App.Pages
private async void OnScanResult(ZXing.Result result) private async void OnScanResult(ZXing.Result result)
{ {
// Stop analysis until we navigate away so we don't keep reading barcodes try
_zxing.IsAnalyzing = false;
var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text))
{ {
if (text.StartsWith("otpauth://totp")) // Stop analysis until we navigate away so we don't keep reading barcodes
_zxing.IsAnalyzing = false;
var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text))
{ {
await QrCodeFoundAsync(); if (text.StartsWith("otpauth://totp"))
_callback(text);
return;
}
else if (Uri.TryCreate(text, UriKind.Absolute, out Uri uri) &&
!string.IsNullOrWhiteSpace(uri?.Query))
{
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
foreach (var part in queryParts)
{ {
if (part.StartsWith("secret=")) await QrCodeFoundAsync();
_callback(text);
return;
}
else if (Uri.TryCreate(text, UriKind.Absolute, out Uri uri) &&
!string.IsNullOrWhiteSpace(uri?.Query))
{
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
foreach (var part in queryParts)
{ {
await QrCodeFoundAsync(); if (part.StartsWith("secret="))
_callback(part.Substring(7)?.ToUpperInvariant()); {
return; await QrCodeFoundAsync();
var subResult = part.Substring(7);
if (!string.IsNullOrEmpty(subResult))
{
_callback(subResult.ToUpperInvariant());
}
return;
}
} }
} }
} }
_callback(null);
}
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
} }
_callback(null);
} }
private async Task QrCodeFoundAsync() private async Task QrCodeFoundAsync()
@@ -228,23 +239,35 @@ namespace Bit.App.Pages
canvas.DrawLine(startXPoint + squareSize, startYPoint + squareSize, startXPoint + squareSize, startYPoint + squareSize - lineSize, strokePaint); canvas.DrawLine(startXPoint + squareSize, startYPoint + squareSize, startXPoint + squareSize, startYPoint + squareSize - lineSize, strokePaint);
} }
} }
async Task AnimationLoopAsync() async Task AnimationLoopAsync()
{ {
_stopwatch.Start(); try
while (_pageIsActive)
{ {
var t = _stopwatch.Elapsed.TotalSeconds % 2 / 2; _stopwatch.Start();
_scale = (20 - (1 - (float)Math.Sin(4 * Math.PI * t))) / 20; while (_pageIsActive)
SkCanvasView.InvalidateSurface();
await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
if (_qrcodeFound && _scale > 0.98f)
{ {
_checkIcon.TextColor = _greenColor; var t = _stopwatch.Elapsed.TotalSeconds % 2 / 2;
_scale = (20 - (1 - (float)Math.Sin(4 * Math.PI * t))) / 20;
SkCanvasView.InvalidateSurface(); SkCanvasView.InvalidateSurface();
break; await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
if (_qrcodeFound && _scale > 0.98f)
{
_checkIcon.TextColor = _greenColor;
SkCanvasView.InvalidateSurface();
break;
}
} }
_stopwatch.Stop();
}
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
}
finally
{
_stopwatch?.Stop();
} }
_stopwatch.Stop();
} }
} }
} }

View File

@@ -7,19 +7,17 @@ namespace Bit.App.Pages
{ {
public class ScanPageViewModel : BaseViewModel public class ScanPageViewModel : BaseViewModel
{ {
private bool _showScanner; private bool _showScanner = true;
private string _totpAuthenticationKey; private string _totpAuthenticationKey;
public ScanPageViewModel() public ScanPageViewModel()
{ {
ShowScanner = true; ToggleScanModeCommand = new Command(() => ShowScanner = !ShowScanner);
ToggleScanModeCommand = new Command(ToggleScanAsync);
} }
public Command ToggleScanModeCommand { get; set; } public Command ToggleScanModeCommand { get; set; }
public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner; public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner;
public string CameraInstructionTop => ShowScanner ? AppResources.CameraInstructionTop : AppResources.OnceTheKeyIsSuccessfullyEntered; public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
public string CameraInstructionBottom => ShowScanner ? AppResources.CameraInstructionBottom : AppResources.SelectAddTotpToStoreTheKeySafely;
public string TotpAuthenticationKey public string TotpAuthenticationKey
{ {
get => _totpAuthenticationKey; get => _totpAuthenticationKey;
@@ -37,8 +35,7 @@ namespace Bit.App.Pages
{ {
nameof(ToggleScanModeLabel), nameof(ToggleScanModeLabel),
nameof(ScanQrPageTitle), nameof(ScanQrPageTitle),
nameof(CameraInstructionTop), nameof(CameraInstructionTop)
nameof(CameraInstructionBottom)
}); });
} }
@@ -54,16 +51,11 @@ namespace Bit.App.Pages
}); });
fs.Spans.Add(new Span fs.Spans.Add(new Span
{ {
Text = ShowScanner ? AppResources.EnterCodeManually : AppResources.ScanQRCode, Text = ShowScanner ? AppResources.EnterKeyManually : AppResources.ScanQRCode,
TextColor = ThemeManager.GetResourceColor("ScanningToggleModeTextColor") TextColor = ThemeManager.GetResourceColor("ScanningToggleModeTextColor")
}); });
return fs; return fs;
} }
} }
private void ToggleScanAsync()
{
ShowScanner = !ShowScanner;
}
} }
} }

View File

@@ -187,11 +187,6 @@
VerticalOptions="Start" /> VerticalOptions="Start" />
<controls:CircularProgressbarView <controls:CircularProgressbarView
Progress="{Binding TotpProgress}" Progress="{Binding TotpProgress}"
ProgressColor="{StaticResource PrimaryColor}"
EndingProgressColor="{StaticResource DangerColor}"
BackgroundProgressColor="{StaticResource BackgroundColor}"
StrokeWidth="3"
Radius="15"
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
@@ -203,6 +198,7 @@
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="2" Grid.RowSpan="2"
StyleClass="text-sm"
VerticalTextAlignment="Center" VerticalTextAlignment="Center"
HorizontalTextAlignment="Center" HorizontalTextAlignment="Center"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
@@ -219,7 +215,7 @@
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" /> AutomationProperties.Name="{u:I18n CopyTotp}" />
<Label <Label
Text="{Binding UpgradeToPremiumTotpText}" Text="{u:I18n UpgradeToPremiumTotpText}"
StyleClass="box-footer-label" StyleClass="box-footer-label"
IsVisible="{Binding ShowUpgradePremiumTotpText}" IsVisible="{Binding ShowUpgradePremiumTotpText}"
Margin="0,5,0,2" Margin="0,5,0,2"

View File

@@ -107,12 +107,12 @@ namespace Bit.App.Pages
}, _mainContent); }, _mainContent);
} }
protected override void OnDisappearing() protected override async void OnDisappearing()
{ {
base.OnDisappearing(); base.OnDisappearing();
IsBusy = false; IsBusy = false;
_broadcasterService.Unsubscribe(nameof(ViewPage)); _broadcasterService.Unsubscribe(nameof(ViewPage));
_vm.CleanUp(); await _vm.StopCiphersTotpTick();
} }
private async void PasswordHistory_Tapped(object sender, System.EventArgs e) private async void PasswordHistory_Tapped(object sender, System.EventArgs e)

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
@@ -22,7 +23,6 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService; private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
@@ -47,13 +47,15 @@ namespace Bit.App.Pages
private byte[] _attachmentData; private byte[] _attachmentData;
private string _attachmentFilename; private string _attachmentFilename;
private bool _passwordReprompted; private bool _passwordReprompted;
private TotpHelper _totpTickHelper;
private CancellationTokenSource _totpTickCancellationToken;
private Task _totpTickTask;
public ViewPageViewModel() public ViewPageViewModel()
{ {
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService"); _auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -208,7 +210,6 @@ namespace Bit.App.Pages
} }
} }
public string UpgradeToPremiumTotpText => AppResources.PremiumSubscriptionRequired;
public bool ShowUpgradePremiumTotpText => !CanAccessPremium && ShowTotp; public bool ShowUpgradePremiumTotpText => !CanAccessPremium && ShowTotp;
public bool ShowUris => IsLogin && Cipher.Login.HasUris; public bool ShowUris => IsLogin && Cipher.Login.HasUris;
public bool ShowIdentityAddress => IsIdentity && ( public bool ShowIdentityAddress => IsIdentity && (
@@ -254,7 +255,6 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(Action finishedLoadingAction = null) public async Task<bool> LoadAsync(Action finishedLoadingAction = null)
{ {
CleanUp();
var cipher = await _cipherService.GetAsync(CipherId); var cipher = await _cipherService.GetAsync(CipherId);
if (cipher == null) if (cipher == null)
{ {
@@ -268,19 +268,10 @@ namespace Bit.App.Pages
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) && if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
(Cipher.OrganizationUseTotp || CanAccessPremium)) (Cipher.OrganizationUseTotp || CanAccessPremium))
{ {
await TotpUpdateCodeAsync(); _totpTickHelper = new TotpHelper(Cipher);
var interval = _totpService.GetTimeInterval(Cipher.Login.Totp); _totpTickCancellationToken?.Cancel();
await TotpTickAsync(interval); _totpTickCancellationToken = new CancellationTokenSource();
_totpInterval = DateTime.UtcNow; _totpTickTask = new TimerTask(StartCiphersTotpTick, _totpTickCancellationToken).Run();
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (_totpInterval == null)
{
return false;
}
var task = TotpTickAsync(interval);
return true;
});
} }
if (_previousCipherId != CipherId) if (_previousCipherId != CipherId)
{ {
@@ -291,9 +282,20 @@ namespace Bit.App.Pages
return true; return true;
} }
public void CleanUp() private async void StartCiphersTotpTick()
{ {
_totpInterval = null; await _totpTickHelper.GenerateNewTotpValues();
TotpSec = _totpTickHelper.TotpSec;
TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
}
public async Task StopCiphersTotpTick()
{
_totpTickCancellationToken?.Cancel();
if (_totpTickTask != null)
{
await _totpTickTask;
}
} }
public async void TogglePassword() public async void TogglePassword()
@@ -421,47 +423,6 @@ namespace Bit.App.Pages
return false; return false;
} }
private async Task TotpUpdateCodeAsync()
{
if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
{
_totpInterval = null;
return;
}
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
{
if (_totpCode.Length > 4)
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
_totpInterval = null;
}
}
private async Task TotpTickAsync(int intervalSeconds)
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % intervalSeconds;
var totpSec = intervalSeconds - mod;
TotpSec = totpSec.ToString();
TotpLow = totpSec < 7;
if (mod == 0)
{
await TotpUpdateCodeAsync();
}
}
private async void CheckPasswordAsync() private async void CheckPasswordAsync()
{ {
if (!(Page as BaseContentPage).DoOnce()) if (!(Page as BaseContentPage).DoOnce())
@@ -647,7 +608,7 @@ namespace Bit.App.Pages
} }
else if (id == "LoginTotp") else if (id == "LoginTotp")
{ {
text = _totpCode; text = TotpCodeFormatted.Replace(" ", string.Empty);
name = AppResources.VerificationCodeTotp; name = AppResources.VerificationCodeTotp;
} }
else if (id == "LoginUri") else if (id == "LoginUri")

View File

@@ -1,6 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@@ -9,9 +10,10 @@
namespace Bit.App.Resources { namespace Bit.App.Resources {
using System; using System;
using System.Reflection;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.1.0.0")] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class AppResources { public class AppResources {
@@ -1449,15 +1451,9 @@ namespace Bit.App.Resources {
} }
} }
public static string CameraInstructionBottom { public static string PointYourCameraAtTheQRCode {
get { get {
return ResourceManager.GetString("CameraInstructionBottom", resourceCulture); return ResourceManager.GetString("PointYourCameraAtTheQRCode", resourceCulture);
}
}
public static string CameraInstructionTop {
get {
return ResourceManager.GetString("CameraInstructionTop", resourceCulture);
} }
} }
@@ -4077,9 +4073,9 @@ namespace Bit.App.Resources {
} }
} }
public static string EnterCodeManually { public static string EnterKeyManually {
get { get {
return ResourceManager.GetString("EnterCodeManually", resourceCulture); return ResourceManager.GetString("EnterKeyManually", resourceCulture);
} }
} }
@@ -4089,9 +4085,9 @@ namespace Bit.App.Resources {
} }
} }
public static string SetupTOTP { public static string SetupTotp {
get { get {
return ResourceManager.GetString("SetupTOTP", resourceCulture); return ResourceManager.GetString("SetupTotp", resourceCulture);
} }
} }
@@ -4106,13 +4102,23 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("SelectAddTotpToStoreTheKeySafely", resourceCulture); return ResourceManager.GetString("SelectAddTotpToStoreTheKeySafely", resourceCulture);
} }
} }
public static string NeverLockWarning public static string NeverLockWarning {
{ get {
get
{
return ResourceManager.GetString("NeverLockWarning", resourceCulture); return ResourceManager.GetString("NeverLockWarning", resourceCulture);
} }
} }
public static string CipherListIsVisibleActivateToShowAuthenticationCodesList {
get {
return ResourceManager.GetString("CipherListIsVisibleActivateToShowAuthenticationCodesList", resourceCulture);
}
}
public static string AuthenticationCodesListIsVisibleActivateToShowCipherList {
get {
return ResourceManager.GetString("AuthenticationCodesListIsVisibleActivateToShowCipherList", resourceCulture);
}
}
} }
} }

View File

@@ -893,11 +893,9 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve"> <data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Cannot read authenticator key.</value> <value>Cannot read authenticator key.</value>
</data> </data>
<data name="CameraInstructionBottom" xml:space="preserve"> <data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Scanning will happen automatically.</value> <value>Point your camera at the QR Code.
</data> Scanning will happen automatically.</value>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Point your camera at the QR code.</value>
</data> </data>
<data name="ScanQrTitle" xml:space="preserve"> <data name="ScanQrTitle" xml:space="preserve">
<value>Scan QR Code</value> <value>Scan QR Code</value>
@@ -2276,22 +2274,29 @@
<data name="AuthenticatorKeyScanner" xml:space="preserve"> <data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value> <value>Authenticator Key</value>
</data> </data>
<data name="EnterCodeManually" xml:space="preserve"> <data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value> <value>Enter Key Manually</value>
</data> </data>
<data name="AddTotp" xml:space="preserve"> <data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value> <value>Add TOTP</value>
</data> </data>
<data name="SetupTOTP" xml:space="preserve"> <data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value> <value>Set up TOTP</value>
</data> </data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve"> <data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,</value> <value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve"> <data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value>select Add TOTP to store the key safely</value> <value></value>
</data> </data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value> <value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data> </data>
<data name="CipherListIsVisibleActivateToShowAuthenticationCodesList" xml:space="preserve">
<value>Cipher list is visible, activate to show authentication codes list.</value>
</data>
<data name="AuthenticationCodesListIsVisibleActivateToShowCipherList" xml:space="preserve">
<value>Authentication codes list is visible, activate to show cipher list.</value>
</data>
</root> </root>

View File

@@ -505,4 +505,17 @@
</Keyboard> </Keyboard>
</Setter> </Setter>
</Style> </Style>
<Style TargetType="controls:CircularProgressbarView">
<Setter Property="ProgressColor"
Value="{DynamicResource PrimaryColor}" />
<Setter Property="EndingProgressColor"
Value="{DynamicResource DangerColor}" />
<Setter Property="BackgroundProgressColor"
Value="{DynamicResource BackgroundColor}" />
<Setter Property="StrokeWidth"
Value="3" />
<Setter Property="Radius"
Value="15" />
</Style>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -0,0 +1,56 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Utilities
{
public class TimerTask
{
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private readonly Action _action;
private readonly CancellationTokenSource _cancellationToken;
public TimerTask(Action action, CancellationTokenSource cancellationToken)
{
_action = action;
_cancellationToken = cancellationToken;
}
public Task Run()
{
return Task.Run(async () =>
{
try
{
while (!_cancellationToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), _cancellationToken.Token);
await Device.InvokeOnMainThreadAsync(() =>
{
if (!_cancellationToken.IsCancellationRequested)
{
try
{
_action?.Invoke();
}
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
}
}
});
Console.WriteLine("TESTES TASK RUNNING");
}
}
catch (TaskCanceledException) { }
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
}
}, _cancellationToken.Token);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Utilities
{
public class TotpHelper
{
private ITotpService _totpService;
private int _interval;
public TotpHelper(CipherView cipher)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
Cipher = cipher;
_interval = _totpService.GetTimeInterval(cipher?.Login?.Totp);
}
public CipherView Cipher { get; private set; }
public string TotpSec { get; private set; }
public string TotpCodeFormatted { get; private set; }
public double Progress { get; private set; }
public async Task GenerateNewTotpValues()
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % _interval;
var totpSec = _interval - mod;
TotpSec = totpSec.ToString();
Progress = totpSec * 100 / 30;
if (mod == 0 || string.IsNullOrEmpty(TotpCodeFormatted))
{
TotpCodeFormatted = await TotpUpdateCodeAsync();
}
}
private async Task<string> TotpUpdateCodeAsync()
{
var totpCode = await _totpService.GetCodeAsync(Cipher?.Login?.Totp);
if (totpCode != null)
{
if (totpCode.Length > 4)
{
var half = (int)Math.Floor(totpCode.Length / 2M);
return string.Format("{0} {1}", totpCode.Substring(0, half),
totpCode.Substring(half));
}
else
{
return totpCode;
}
}
else
{
return null;
}
}
}
}