Skip to content

Commit be0aa43

Browse files
Vetle444Vetle Finstad
andauthored
Add BarcodeScanner feedback guidance (#895)
Co-authored-by: Vetle Finstad <finstad@Vetles-MacBook-Pro-2.local>
1 parent d45a17c commit be0aa43

14 files changed

Lines changed: 627 additions & 15 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## [60.4.0]
2+
- [BarcodeScanner] Shows validation failure messages above the scan rectangle when rejected barcode results include an error message, adds success/error haptics, and adds automatic camera zoom tips with `BarcodeScannerStartOptions.Hint`.
3+
14
## [60.3.0]
25
- [Bottomsheet] Added page navigation inside bottomsheet
36
- [Checkmark] Added `AddStrokeToCheckmark` property to display a visible stroke around the checkmark
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml
2+
version="1.0"
3+
encoding="utf-8"?>
4+
5+
<dui:ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
6+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
7+
xmlns:dui="http://dips.com/mobile.ui"
8+
x:Class="Components.ComponentsSamples.BarcodeScanning.BarcodeFeedbackSample">
9+
<dui:ContentPage.ToolbarItems>
10+
<ToolbarItem IconImageSource="{dui:Icons close_line}"
11+
Clicked="Close" />
12+
</dui:ContentPage.ToolbarItems>
13+
<dui:CameraPreview x:Name="CameraPreview" />
14+
</dui:ContentPage>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using Components.Resources.LocalizedStrings;
2+
using DIPS.Mobile.UI.API.Camera;
3+
using DIPS.Mobile.UI.API.Camera.BarcodeScanning;
4+
using DIPS.Mobile.UI.Resources.Colors;
5+
using DIPS.Mobile.UI.Resources.Styles;
6+
using DIPS.Mobile.UI.Resources.Styles.Label;
7+
using DuiColors = DIPS.Mobile.UI.Resources.Colors.Colors;
8+
using DuiLabel = DIPS.Mobile.UI.Components.Labels.Label;
9+
10+
namespace Components.ComponentsSamples.BarcodeScanning;
11+
12+
public partial class BarcodeFeedbackSample
13+
{
14+
private readonly BarcodeScanner m_barcodeScanner;
15+
private View? m_tooltipView;
16+
private int m_validationAttempt;
17+
18+
public BarcodeFeedbackSample()
19+
{
20+
InitializeComponent();
21+
m_barcodeScanner = new BarcodeScanner();
22+
}
23+
24+
private async Task Start()
25+
{
26+
try
27+
{
28+
await m_barcodeScanner.Start(new BarcodeScannerStartOptions
29+
{
30+
Preview = CameraPreview,
31+
OnCameraFailed = CameraFailed,
32+
OnBarcodeAcceptedAsync = HandleBarcodeAcceptedAsync,
33+
ValidateBarcodeAsync = ValidateBarcodeAsync,
34+
OnBarcodeRejectedAsync = HandleBarcodeRejectedAsync,
35+
Strategy = new ScanRectangleBarcodeScanStrategy
36+
{
37+
WidthFraction = 0.8f,
38+
HeightFraction = 0.3f
39+
},
40+
Hint = new BarcodeScannerHintOptions
41+
{
42+
ShowAutomaticHint = true,
43+
HintText = LocalizedStrings.BarcodeScannerFeedbackFocusHint,
44+
Delay = TimeSpan.FromSeconds(5)
45+
}
46+
});
47+
48+
m_barcodeScanner.SetTooltipView(GetTooltipView());
49+
}
50+
catch (Exception exception)
51+
{
52+
await Application.Current?.MainPage?.DisplayAlert("Failed, check console!", exception.Message, "Ok")!;
53+
Console.WriteLine(exception);
54+
}
55+
}
56+
57+
private View GetTooltipView()
58+
{
59+
if (m_tooltipView is not null)
60+
return m_tooltipView;
61+
62+
m_tooltipView = new DuiLabel
63+
{
64+
Text = LocalizedStrings.BarcodeScannerFeedbackTooltipText,
65+
Style = Styles.GetLabelStyle(LabelStyle.UI200),
66+
TextColor = DuiColors.GetColor(ColorName.color_text_on_button),
67+
HorizontalTextAlignment = TextAlignment.Center,
68+
LineBreakMode = LineBreakMode.WordWrap
69+
};
70+
71+
return m_tooltipView;
72+
}
73+
74+
private void CameraFailed(CameraException e)
75+
{
76+
App.Current.MainPage.DisplayAlert("Something failed!", e.Message, "Ok");
77+
}
78+
79+
private Task HandleBarcodeAcceptedAsync(BarcodeScanResult barcodeScanResult)
80+
{
81+
Console.WriteLine($"Accepted barcode: {barcodeScanResult.Barcode.RawValue}");
82+
return Task.CompletedTask;
83+
}
84+
85+
private async Task<BarcodeScanValidationResult> ValidateBarcodeAsync(string barcode)
86+
{
87+
await Task.Delay(150);
88+
89+
var feedback = (m_validationAttempt++ % 4) switch
90+
{
91+
0 => (Message: LocalizedStrings.BarcodeScannerFeedbackAlreadyScanned, ReasonCode: "already-scanned"),
92+
1 => (Message: LocalizedStrings.BarcodeScannerFeedbackWrongPatient, ReasonCode: "wrong-patient"),
93+
2 => (Message: LocalizedStrings.BarcodeScannerFeedbackNotLabel, ReasonCode: "not-label"),
94+
_ => default
95+
};
96+
97+
return feedback.Message is null
98+
? BarcodeScanValidationResult.Valid()
99+
: BarcodeScanValidationResult.Invalid(feedback.Message, feedback.ReasonCode);
100+
}
101+
102+
private Task HandleBarcodeRejectedAsync(BarcodeScanResult barcodeScanResult, BarcodeScanValidationResult validationResult)
103+
{
104+
Console.WriteLine($"Rejected barcode: {barcodeScanResult.Barcode.RawValue}. {validationResult.ReasonCode}");
105+
return Task.CompletedTask;
106+
}
107+
108+
protected override void OnAppearing()
109+
{
110+
_ = Start();
111+
base.OnAppearing();
112+
}
113+
114+
protected override void OnDisappearing()
115+
{
116+
m_barcodeScanner.StopAndDispose();
117+
base.OnDisappearing();
118+
}
119+
120+
private void Close(object? sender, EventArgs e)
121+
{
122+
Shell.Current.Navigation.PopModalAsync();
123+
}
124+
}

src/app/Components/ComponentsSamples/BarcodeScanning/BarcodeScanningHubSamples.xaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<dui:ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
66
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
77
xmlns:dui="http://dips.com/mobile.ui"
8+
xmlns:localizedStrings="clr-namespace:Components.Resources.LocalizedStrings"
89
x:Class="Components.ComponentsSamples.BarcodeScanning.BarcodeScanningHubSamples">
910
<dui:VerticalStackLayout Spacing="0"
1011
Margin="{dui:Thickness Left=content_margin_medium, Right=content_margin_medium, Top=content_margin_large}"
@@ -25,6 +26,11 @@
2526
Subtitle="Scan rectangle with tooltip view above"
2627
HasBottomDivider="True" />
2728

29+
<dui:NavigationListItem x:Name="FeedbackScannerItem"
30+
Title="{x:Static localizedStrings:LocalizedStrings.BarcodeScannerFeedbackTitle}"
31+
Subtitle="{x:Static localizedStrings:LocalizedStrings.BarcodeScannerFeedbackSubtitle}"
32+
HasBottomDivider="True" />
33+
2834
<dui:NavigationListItem x:Name="CounterScannerItem"
2935
Title="Barcode Counter"
3036
Subtitle="Scan continuously and count barcodes" />

src/app/Components/ComponentsSamples/BarcodeScanning/BarcodeScanningHubSamples.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public BarcodeScanningHubSamples()
88
BasicScannerItem.Command = new Command(() => OpenModal(new BarcodeScanningSample()));
99
OverlayScannerItem.Command = new Command(() => OpenModal(new BarcodeOverlaySample()));
1010
TooltipScannerItem.Command = new Command(() => OpenModal(new BarcodeTooltipSample()));
11+
FeedbackScannerItem.Command = new Command(() => OpenModal(new BarcodeFeedbackSample()));
1112
CounterScannerItem.Command = new Command(() => OpenModal(new BarcodeCounterSample()));
1213
}
1314

src/app/Components/Resources/LocalizedStrings/LocalizedStrings.Designer.cs

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/Components/Resources/LocalizedStrings/LocalizedStrings.en.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,27 @@
318318
<data name="BarcodeScanning" xml:space="preserve">
319319
<value>Barcode Scanning</value>
320320
</data>
321+
<data name="BarcodeScannerFeedbackTitle" xml:space="preserve">
322+
<value>Barcode scanner feedback</value>
323+
</data>
324+
<data name="BarcodeScannerFeedbackSubtitle" xml:space="preserve">
325+
<value>Shows rejection reasons above the scan area</value>
326+
</data>
327+
<data name="BarcodeScannerFeedbackTooltipText" xml:space="preserve">
328+
<value>Place the label inside the frame and hold the camera steady.</value>
329+
</data>
330+
<data name="BarcodeScannerFeedbackFocusHint" xml:space="preserve">
331+
<value>Hold the label farther away to focus.</value>
332+
</data>
333+
<data name="BarcodeScannerFeedbackAlreadyScanned" xml:space="preserve">
334+
<value>The label has already been scanned.</value>
335+
</data>
336+
<data name="BarcodeScannerFeedbackWrongPatient" xml:space="preserve">
337+
<value>The label does not belong to this patient.</value>
338+
</data>
339+
<data name="BarcodeScannerFeedbackNotLabel" xml:space="preserve">
340+
<value>The code you scanned is not a label.</value>
341+
</data>
321342
<data name="Barcode" xml:space="preserve">
322343
<value>Barcode</value>
323344
</data>

src/app/Components/Resources/LocalizedStrings/LocalizedStrings.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,27 @@
323323
<data name="BarcodeScanning" xml:space="preserve">
324324
<value>Strekkode skanning</value>
325325
</data>
326+
<data name="BarcodeScannerFeedbackTitle" xml:space="preserve">
327+
<value>Strekkode skanning med tilbakemelding</value>
328+
</data>
329+
<data name="BarcodeScannerFeedbackSubtitle" xml:space="preserve">
330+
<value>Viser avvisningsårsaker over skanneområdet</value>
331+
</data>
332+
<data name="BarcodeScannerFeedbackTooltipText" xml:space="preserve">
333+
<value>Plasser etiketten innenfor rammen og hold kameraet rolig.</value>
334+
</data>
335+
<data name="BarcodeScannerFeedbackFocusHint" xml:space="preserve">
336+
<value>Hold etiketten lenger unna for å fokusere.</value>
337+
</data>
338+
<data name="BarcodeScannerFeedbackAlreadyScanned" xml:space="preserve">
339+
<value>Etiketten er allerede skannet.</value>
340+
</data>
341+
<data name="BarcodeScannerFeedbackWrongPatient" xml:space="preserve">
342+
<value>Etiketten hører ikke til denne pasienten.</value>
343+
</data>
344+
<data name="BarcodeScannerFeedbackNotLabel" xml:space="preserve">
345+
<value>Koden du skannet er ikke en etikett.</value>
346+
</data>
326347
<data name="Barcode" xml:space="preserve">
327348
<value>Strekkode</value>
328349
</data>

src/library/DIPS.Mobile.UI/API/Camera/BarcodeScanning/BarcodeScanSession.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using DIPS.Mobile.UI.API.Camera.Preview;
2+
using DIPS.Mobile.UI.API.Vibration;
23
using DIPS.Mobile.UI.Internal.Logging;
34

45
namespace DIPS.Mobile.UI.API.Camera.BarcodeScanning;
@@ -110,6 +111,7 @@ private async Task<BarcodeScanValidationResult> ValidateBarcodeScanResultAsync(B
110111
private async Task<bool> AcceptBarcodeScanResultAsync(BarcodeScanResult barcodeScanResult, BarcodeScanRectangleOverlay? scanRectangleOverlay)
111112
{
112113
State = BarcodeScannerState.SuccessAnimating;
114+
RunFeedbackVibration(VibrationService.Success);
113115

114116
var successAnimationTask = scanRectangleOverlay?.PlaySuccessAndResetAsync() ?? Task.CompletedTask;
115117
if (ShouldShowProgressCounter && scanRectangleOverlay is not null)
@@ -150,6 +152,7 @@ private async Task<bool> AcceptBarcodeScanResultAsync(BarcodeScanResult barcodeS
150152
private async Task<bool> RejectBarcodeScanResultAsync(BarcodeScanResult barcodeScanResult, BarcodeScanValidationResult validationResult, BarcodeScanRectangleOverlay? scanRectangleOverlay)
151153
{
152154
State = BarcodeScannerState.FailureAnimating;
155+
RunFeedbackVibration(VibrationService.Error);
153156

154157
if (scanRectangleOverlay is not null)
155158
{
@@ -163,6 +166,21 @@ private async Task<bool> RejectBarcodeScanResultAsync(BarcodeScanResult barcodeS
163166
return !m_isDisposed && State != BarcodeScannerState.Paused;
164167
}
165168

169+
private static void RunFeedbackVibration(Action vibration)
170+
{
171+
MainThread.BeginInvokeOnMainThread(() =>
172+
{
173+
try
174+
{
175+
vibration.Invoke();
176+
}
177+
catch (Exception exception)
178+
{
179+
DUILogService.LogError<BarcodeScanner>($"Barcode scanner feedback vibration failed: {exception.Message}");
180+
}
181+
});
182+
}
183+
166184
private async Task NotifyBarcodeAcceptedAsync(BarcodeScanResult barcodeScanResult)
167185
{
168186
try

0 commit comments

Comments
 (0)