Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public partial class AppShell : Shell
CreateViewModelMapping<AvatarViewShadowsPage, AvatarViewShadowsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<AvatarViewShapesPage, AvatarViewShapesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<AvatarViewSizesPage, AvatarViewSizesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<BarcodeScanningPage, BarcodeScanningViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<BasicMapsPage, BasicMapsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<CameraViewPage, CameraViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<DrawingViewPage, DrawingViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
<ProjectReference Include="..\..\src\CommunityToolkit.Maui.Maps\CommunityToolkit.Maui.Maps.csproj" />
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<!-- Required NuGet Packages -->
<PackageReference Include="Xamarin.Google.MLKit.BarcodeScanning" Version="117.3.0.3" />
</ItemGroup>

<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))=='windows' and $(Configuration) == 'Release'">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
services.AddTransientWithShellRoute<NavigationBarPage, NavigationBarAndroidViewModel>();

// Add Views Pages + ViewModels
services.AddTransientWithShellRoute<BarcodeScanningPage, BarcodeScanningViewModel>();
services.AddTransientWithShellRoute<BasicMapsPage, BasicMapsViewModel>();
services.AddTransientWithShellRoute<CameraViewPage, CameraViewViewModel>();
services.AddTransientWithShellRoute<DrawingViewPage, DrawingViewViewModel>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
xmlns:sample="clr-namespace:CommunityToolkit.Maui.Sample"
Title="Barcode Scanning"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.BarcodeScanningPage"
x:TypeArguments="viewModels:BarcodeScanningViewModel"
x:DataType="viewModels:BarcodeScanningViewModel">

<Grid RowDefinitions="200,*,Auto,Auto" ColumnDefinitions="3*,*">
<toolkit:CameraView
x:Name="Camera"
Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Grid.RowSpan="3">
<toolkit:CameraView.Scenarios>
<sample:PlatformBarcodeScanningScenario Command="{Binding CodeDetectedCommand}" />
</toolkit:CameraView.Scenarios>
</toolkit:CameraView>
</Grid>
</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class BarcodeScanningPage : BasePage<BarcodeScanningViewModel>
{
public BarcodeScanningPage(BarcodeScanningViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}

protected override async void OnAppearing()
{
base.OnAppearing();

var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await BindingContext.RefreshCamerasCommand.ExecuteAsync(cancellationTokenSource.Token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Windows.Input;
using CommunityToolkit.Maui.Core;

namespace CommunityToolkit.Maui.Sample;

/// <summary>
///
/// </summary>
public partial class PlatformBarcodeScanningScenario : PlatformCameraScenario
{
/// <summary>
/// Backing BindableProperty for the <see cref="Command"/> property.
/// </summary>
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(PlatformBarcodeScanningScenario));

/// <summary>
/// The Command that should be executed when a barcode is detected.
/// </summary>
public ICommand? Command
{
get => (ICommand?)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Diagnostics;
using System.Windows.Input;
using Android.Gms.Tasks;
using Android.Runtime;
using AndroidX.Camera.Core;
using Xamarin.Google.MLKit.Vision.Barcode.Common;
using Xamarin.Google.MLKit.Vision.BarCode;
using Xamarin.Google.MLKit.Vision.Common;

using Scanner = Xamarin.Google.MLKit.Vision.BarCode.BarcodeScanning;

namespace CommunityToolkit.Maui.Sample;

public class BarcodeAnalyzer : Java.Lang.Object, ImageAnalysis.IAnalyzer
{
readonly ICommand command;
IBarcodeScanner? barcodeScanner;
readonly Lock resultsLock = new();

public BarcodeAnalyzer(ICommand command)
{
this.command = command;
}

internal void UpdateSymbologies()
{
barcodeScanner?.Dispose();
barcodeScanner = Scanner.GetClient(new BarcodeScannerOptions.Builder()
.SetBarcodeFormats(Barcode.FormatAllFormats)
.Build());
}

public void Analyze(IImageProxy proxy)
{
try
{
ArgumentNullException.ThrowIfNull(proxy?.Image);
ArgumentNullException.ThrowIfNull(barcodeScanner);

using var inputImage = InputImage.FromMediaImage(proxy.Image, 0);
using var task = barcodeScanner.Process(inputImage);
var result = TasksClass.Await(task);

lock (resultsLock)
{
ProcessResults(result);
}

result?.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
try
{
proxy?.Close();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}

void ProcessResults(Java.Lang.Object? result)
{
if (result is not JavaList javaList)
{
return;
}

foreach (Barcode barcode in javaList)
{
if (barcode is null)
{
continue;
}

if (string.IsNullOrEmpty(barcode.DisplayValue) && string.IsNullOrEmpty(barcode.RawValue))
{
continue;
}

if (this.command?.CanExecute(barcode.RawValue) is true)
{
this.command.Execute(barcode.RawValue);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using AndroidX.Camera.Core;

namespace CommunityToolkit.Maui.Sample;

public partial class PlatformBarcodeScanningScenario
{
readonly Lazy<UseCase>? lazyUseCase = null;

public PlatformBarcodeScanningScenario()
{
lazyUseCase = new Lazy<UseCase>(() =>
{
var imageAnalysis =
new ImageAnalysis.Builder()
.SetBackpressureStrategy(ImageAnalysis.StrategyKeepOnlyLatest)
.Build();

if (imageAnalysis.BackgroundExecutor is null)
{
throw new InvalidOperationException("Background executor must be set before use case.");
}

imageAnalysis.SetAnalyzer(imageAnalysis.BackgroundExecutor, new BarcodeAnalyzer(Command!));

return imageAnalysis;
});
}

public override UseCase UseCase => lazyUseCase?.Value!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Windows.Input;
using AVFoundation;
using CoreFoundation;

namespace CommunityToolkit.Maui.Sample;

/// <summary>
/// Apple based implementation
/// </summary>
public partial class PlatformBarcodeScanningScenario
{
readonly Lazy<AVCaptureMetadataOutput> lazyOutput;

public PlatformBarcodeScanningScenario()
{
lazyOutput = new Lazy<AVCaptureMetadataOutput>(() =>
{
var output = new AVCaptureMetadataOutput();

output.SetDelegate(new BarcodeDetectionDelegate(Command), DispatchQueue.MainQueue);
output.MetadataObjectTypes =
AVMetadataObjectType.QRCode | AVMetadataObjectType.EAN13Code;

return output;
});
}

public override AVCaptureOutput Output => lazyOutput.Value;
}

sealed class BarcodeDetectionDelegate : AVCaptureMetadataOutputObjectsDelegate
{
readonly ICommand? command;

public BarcodeDetectionDelegate(ICommand? command)
{
this.command = command;
}

public override void DidOutputMetadataObjects(
AVCaptureMetadataOutput captureOutput,
AVMetadataObject[] metadataObjects,
AVCaptureConnection connection)
{
foreach (var metadataObject in metadataObjects)
{
if (metadataObject is AVMetadataMachineReadableCodeObject readableObject)
{
var code = readableObject.StringValue;

Console.WriteLine($"Metadata object {code} at {string.Join(",", readableObject.Corners?? [])}");

if (this.command?.CanExecute(readableObject.StringValue) is true)
{
this.command.Execute(readableObject.StringValue);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Windows.Input;
using CommunityToolkit.Maui.Core;

namespace CommunityToolkit.Maui.Sample;

/// <summary>
///
/// </summary>
public partial class PlatformBarcodeScanningScenario : PlatformCameraScenario
{
public ICommand? Command { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Windows.Input;
using AVFoundation;
using CoreFoundation;

namespace CommunityToolkit.Maui.Sample;

/// <summary>
/// Apple based implementation
/// </summary>
public partial class PlatformBarcodeScanningScenario
{
readonly Lazy<AVCaptureMetadataOutput> lazyOutput;

public PlatformBarcodeScanningScenario()
{
lazyOutput = new Lazy<AVCaptureMetadataOutput>(() =>
{
var output = new AVCaptureMetadataOutput();

output.SetDelegate(new BarcodeDetectionDelegate(Command), DispatchQueue.MainQueue);
output.MetadataObjectTypes =
AVMetadataObjectType.QRCode | AVMetadataObjectType.EAN13Code;

return output;
});
}

public override AVCaptureOutput Output => lazyOutput.Value;
}

sealed class BarcodeDetectionDelegate : AVCaptureMetadataOutputObjectsDelegate
{
readonly ICommand? command;

public BarcodeDetectionDelegate(ICommand? command)
{
this.command = command;
}

public override void DidOutputMetadataObjects(
AVCaptureMetadataOutput captureOutput,
AVMetadataObject[] metadataObjects,
AVCaptureConnection connection)
{
foreach (var metadataObject in metadataObjects)
{
if (metadataObject is AVMetadataMachineReadableCodeObject readableObject)
{
var code = readableObject.StringValue;

Console.WriteLine($"Metadata object {code} at {string.Join(",", readableObject.Corners?? [])}");

if (this.command?.CanExecute(readableObject.StringValue) is true)
{
this.command.Execute(readableObject.StringValue);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public partial class BarcodeScanningViewModel(ICameraProvider cameraProvider) : BaseViewModel
{
readonly ICameraProvider cameraProvider = cameraProvider;

[ObservableProperty]
public partial string DetectedCode { get; set; }

[RelayCommand]
void OnCodeDetected(string code)
{
DetectedCode = code;
}

[RelayCommand]
async Task RefreshCameras(CancellationToken token) => await cameraProvider.RefreshAvailableCameras(token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public sealed partial class ViewsGalleryViewModel() : BaseGalleryViewModel(
SectionModel.Create<AvatarViewShadowsViewModel>("AvatarView Shadows Page", Colors.Red, "A page demonstrating AvatarViews with various shadow options."),
SectionModel.Create<AvatarViewShapesViewModel>("AvatarView Shapes Page", Colors.Red, "A page demonstrating AvatarViews with various shape options."),
SectionModel.Create<AvatarViewSizesViewModel>("AvatarView Sizes Page", Colors.Red, "A page demonstrating AvatarViews with various size options."),
SectionModel.Create<BarcodeScanningViewModel>("Barcode Page", Colors.Red, "Barcode scanning support using the CameraView."),
SectionModel.Create<CameraViewViewModel>("CameraView Page", Colors.Red, "CameraView is a view for displaying camera output."),
SectionModel.Create<DrawingViewViewModel>("DrawingView", Colors.Red, "DrawingView provides a canvas for users to \"paint\" on the screen. The drawing can also be captured and displayed as an Image."),
SectionModel.Create<ExpanderViewModel>("Expander Page", Colors.Red, "Expander allows collapse and expand content."),
Expand Down
Loading
Loading