1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

PS-70 Added circular progress to the OTP count down

This commit is contained in:
André Bispo
2022-06-13 12:57:07 +01:00
parent 77c8156515
commit a3218aed26
10 changed files with 190 additions and 140 deletions

View File

@@ -27,6 +27,8 @@
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<controls:IconLabel
@@ -51,7 +53,13 @@
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid
RowSpacing="0"
ColumnSpacing="0"
Grid.Row="0"
Grid.Column="1"
VerticalOptions="Center"
Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
@@ -76,36 +84,46 @@
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.Name}" />
<controls:IconLabel
Grid.Column="1"
Grid.Row="1"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" />
</Grid>
<controls:CircularProgressbarView
Progress="{Binding Progress}"
Margin="0, 10, 0, 0"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Margin="0, 0, 10, 0"
Margin="0, 0, 0, 10"
Grid.Row="0"
Grid.Column="1"
Grid.Column="2"
Grid.RowSpan="2"
HorizontalOptions="End"
HorizontalTextAlignment="End"
StyleClass="text-sm"
HorizontalTextAlignment="Center"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding TotpCodeFormatted, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Margin="0, 0, 0, 0"
Grid.Row="0"
Grid.Column="3"
Grid.RowSpan="2"
StyleClass="text-sm"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
VerticalOptions="CenterAndExpand" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="2"
Grid.Column="4"
Grid.RowSpan="2"
Padding="0,0,1,0"
AutomationProperties.IsInAccessibleTree="True"

View File

@@ -1,4 +1,5 @@
using System;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@@ -111,7 +112,7 @@ namespace Bit.App.Controls
private void MoreButton_Clicked(object sender, EventArgs e)
{
var cipher = ((sender as MiButton)?.BindingContext as AuthenticatorViewCellViewModel)?.Cipher;
var cipher = ((sender as MiButton)?.BindingContext as GroupingsPageTOTPListItem)?.Cipher;
if (cipher != null)
{
//ButtonCommand?.Execute(cipher);

View File

@@ -1,103 +0,0 @@
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AuthenticatorViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
private string _totpCodeFormatted = "938 928";
private string _totpSec;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public AuthenticatorViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value);
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public void UpdateTotpSec(long totpSec)
{
_totpSec = totpSec.ToString();
}
//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;
// }
//}
}
}

View File

@@ -0,0 +1,10 @@
<?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

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using SkiaSharp;
using SkiaSharp.Views.Forms;
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 SKColor Color => Progress > 90 ? SKColors.Red : SKColors.Blue;
public double Progress
{
get { return (double)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var context = bindable as CircularProgressbarView;
context.SkCanvasView.InvalidateSurface();
}
public CircularProgressbarView()
{
InitializeComponent();
var circle = new Circle(25, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
_progressDrawer = new ProgressDrawer(SkCanvasView, circle, () => (float)Progress, 5, SKColors.White, Color);
}
}
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 class ProgressDrawer
{
public ProgressDrawer(SKCanvasView canvas, Circle circle, Func<float> progress, float strokeWidth, SKColor progressColor, SKColor foregroundColor)
{
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);
};
}
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)
{
var angle = progress.Invoke() * 3.6f;
canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint() { StrokeWidth = strokewidth, Color = color, IsStroke = true, IsAntialias = true });
}
}
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)
{
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);
};
}
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)
{
var angle = progress.Invoke() * 3.6f;
canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint() { StrokeWidth = strokewidth, Color = color, IsStroke = true,
IsAntialias = true
});
}
}
}

View File

@@ -185,6 +185,7 @@ namespace Bit.App.Pages
{
base.OnDisappearing();
IsBusy = false;
_vm.TOTPFilterEnable = false;
_broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();

View File

@@ -2,6 +2,7 @@
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{

View File

@@ -1,10 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -13,23 +10,15 @@ namespace Bit.App.Pages
{
public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem
{
//private string _totpCode;
private readonly ITotpService _totpService;
//public CipherView Cipher { get; set; }
//public CipherType? Type { get; set; }
//public int interval { get; set; }
//public long TotpSec { get; set; }
//private DateTime? _totpInterval = null;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public int interval { get; set; }
private double _progress;
private string _totpSec;
private string _totpCode;
private string _totpCodeFormatted = "938 928";
@@ -62,7 +51,11 @@ namespace Bit.App.Pages
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public double Progress
{
get => _progress;
set => SetProperty(ref _progress, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
@@ -95,6 +88,7 @@ namespace Bit.App.Pages
var mod = epoc % interval;
var totpSec = interval - mod;
TotpSec = totpSec.ToString();
Progress = totpSec * 100 / 30;
//TotpLow = totpSec < 7;
if (mod == 0)
{

View File

@@ -83,7 +83,7 @@ namespace Bit.App.Pages
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
TOTPFilterCommand = new AsyncCommand(LoadDataAsync,
TOTPFilterCommand = new AsyncCommand(LoadAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
@@ -324,18 +324,15 @@ namespace Bit.App.Pages
foreach (var item in ciphersListItems)
{
item.TotpUpdateCodeAsync();
await item.TotpUpdateCodeAsync();
}
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (Type != CipherType.Login)
return false;
foreach (var item in ciphersListItems)
{
item.TotpTickAsync();
}
return true;
return TOTPFilterEnable;
});
}
else

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -89,6 +90,7 @@ namespace Bit.App.Pages
{
if (text.StartsWith("otpauth://totp"))
{
Vibration.Vibrate();
_callback(text);
return;
}
@@ -100,6 +102,7 @@ namespace Bit.App.Pages
{
if (part.StartsWith("secret="))
{
Vibration.Vibrate();
_callback(part.Substring(7)?.ToUpperInvariant());
return;
}