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:
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user