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:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
10
src/App/Controls/CircularProgressbarView.xaml
Normal file
10
src/App/Controls/CircularProgressbarView.xaml
Normal 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>
|
||||
128
src/App/Controls/CircularProgressbarView.xaml.cs
Normal file
128
src/App/Controls/CircularProgressbarView.xaml.cs
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
base.OnDisappearing();
|
||||
IsBusy = false;
|
||||
_vm.TOTPFilterEnable = false;
|
||||
_broadcasterService.Unsubscribe(_pageName);
|
||||
_vm.DisableRefreshing();
|
||||
_accountAvatar?.OnDisappearing();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user