1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-06 10:34:07 +00:00

PS-70 Added scanner square corner overlay. Added scanning animation. Added scan success animation.

This commit is contained in:
André Bispo
2022-06-21 22:28:13 +01:00
parent 46c73dafbe
commit 277dd5942f
2 changed files with 114 additions and 7 deletions

View File

@@ -6,6 +6,8 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:forms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
xmlns:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
x:Name="_page"
Title="{u:I18n ScanQrTitle}">
@@ -27,14 +29,12 @@
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<zxing:ZXingScannerView
x:Name="_zxing"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
AutomationId="zxingScannerView"
OnScanResult="OnScanResult">
</zxing:ZXingScannerView>
OnScanResult="OnScanResult"/>
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
@@ -78,6 +78,31 @@
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackLayout
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="30,0">
<forms:SKCanvasView x:Name="SkCanvasView"
Margin="0,50,0,0"
WidthRequest="250"
HeightRequest="250"
VerticalOptions="Center"
HorizontalOptions="Center"
PaintSurface="OnCanvasViewPaintSurface"/>
<controls:IconButton
x:Name="_checkIcon"
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
HorizontalOptions="Center"
VerticalOptions="Start"
FontSize="30"
TextColor="Transparent"/>
</StackLayout>
<BoxView
Grid.Column="0"
Grid.Row="2"

View File

@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -16,6 +20,9 @@ namespace Bit.App.Pages
private CancellationTokenSource _autofocusCts;
private Task _continuousAutofocusTask;
private Color _greenColor;
private SKColor _blueSKColor;
private SKColor _greenSKColor;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
@@ -34,6 +41,10 @@ namespace Bit.App.Pages
{
ToolbarItems.RemoveAt(0);
}
_greenColor = ThemeManager.GetResourceColor("SuccessColor");
_greenSKColor = _greenColor.ToSKColor();
_blueSKColor = ThemeManager.GetResourceColor("PrimaryColor").ToSKColor();
}
protected override void OnAppearing()
@@ -69,6 +80,8 @@ namespace Bit.App.Pages
_logger.Value.Exception(ex);
}
}, autofocusCts.Token);
_pageIsActive = true;
AnimationLoop();
}
protected override async void OnDisappearing()
@@ -78,6 +91,7 @@ namespace Bit.App.Pages
await _continuousAutofocusTask;
_zxing.IsScanning = false;
_pageIsActive = false;
base.OnDisappearing();
}
@@ -91,8 +105,12 @@ namespace Bit.App.Pages
{
if (text.StartsWith("otpauth://totp"))
{
Vibration.Vibrate();
_callback(text);
Task.Run(async () =>
{
_qrcodeFound = true;
await Task.Delay(2000);
_callback(text);
});
return;
}
else if (Uri.TryCreate(text, UriKind.Absolute, out Uri uri) &&
@@ -103,8 +121,12 @@ namespace Bit.App.Pages
{
if (part.StartsWith("secret="))
{
Vibration.Vibrate();
_callback(part.Substring(7)?.ToUpperInvariant());
Task.Run(async () =>
{
_qrcodeFound = true;
await Task.Delay(2000);
_callback(part.Substring(7)?.ToUpperInvariant());
});
return;
}
}
@@ -125,5 +147,65 @@ namespace Bit.App.Pages
{
ViewModel.ToggleScanModeCommand.Execute(null);
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
var margins = 20;
var maxSquareSize = (Math.Min(info.Height, info.Width)*0.9f - margins) * _scale;
var squareSize = maxSquareSize;
var lineSize = squareSize * 0.15f;
var startXPoint = (info.Width / 2) - (squareSize / 2);
var startYPoint = (info.Height / 2) - (squareSize / 2);
canvas.Clear(SKColors.Transparent);
using (SKPaint strokePaint = new SKPaint
{
Color = _qrcodeFound ? _greenSKColor : _blueSKColor,
StrokeWidth = 9*_scale,
StrokeCap = SKStrokeCap.Round,
})
{
canvas.Scale(1,1);
//top left
canvas.DrawLine (startXPoint, startYPoint, startXPoint, startYPoint+lineSize, strokePaint);
canvas.DrawLine (startXPoint, startYPoint, startXPoint+lineSize, startYPoint, strokePaint);
//bot left
canvas.DrawLine (startXPoint, startYPoint+squareSize, startXPoint, startYPoint+squareSize-lineSize, strokePaint);
canvas.DrawLine (startXPoint, startYPoint+squareSize, startXPoint+lineSize, startYPoint+squareSize, strokePaint);
//top right
canvas.DrawLine (startXPoint+squareSize, startYPoint, startXPoint+squareSize-lineSize, startYPoint, strokePaint);
canvas.DrawLine (startXPoint+squareSize, startYPoint, startXPoint+squareSize, startYPoint+lineSize, strokePaint);
//bot right
canvas.DrawLine (startXPoint+squareSize, startYPoint+squareSize, startXPoint+squareSize-lineSize, startYPoint+squareSize, strokePaint);
canvas.DrawLine (startXPoint+squareSize, startYPoint+squareSize, startXPoint+squareSize, startYPoint+squareSize-lineSize, strokePaint);
}
}
Stopwatch _stopwatch = new Stopwatch();
bool _pageIsActive;
bool _qrcodeFound = false;
float _scale;
async Task AnimationLoop()
{
_stopwatch.Start();
while (_pageIsActive)
{
var t = _stopwatch.Elapsed.TotalSeconds % 2 / 2;
_scale = (20-(1-(float)Math.Sin(4*Math.PI*t))) / 20;
SkCanvasView.InvalidateSurface();
await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
if (_qrcodeFound && _scale > 0.98f)
{
Vibration.Vibrate();
_checkIcon.TextColor = _greenColor;
SkCanvasView.InvalidateSurface();
break;
}
}
_stopwatch.Stop();
}
}
}