From b673fd2da3761597e000fbf112e768b68ff0fbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Thu, 17 Jul 2025 19:17:27 +0200 Subject: [PATCH] Scan ean code on iOS app --- Bimix.UI.Mobile/Bimix.UI.Mobile.csproj | 21 ++- Bimix.UI.Mobile/MauiProgram.cs | 16 +- Bimix.UI.Mobile/Services/ScannerService.cs | 140 ++++++++++++++++++ .../Components/ProductListComponent.razor | 27 +++- .../Components/ProductListComponent.razor.cs | 44 +++++- Bimix.UI.Shared/Interfaces/IScannerService.cs | 9 ++ .../Services/NoOpScannerService.cs | 16 ++ Bimix.UI.Web/Program.cs | 5 +- 8 files changed, 255 insertions(+), 23 deletions(-) create mode 100644 Bimix.UI.Mobile/Services/ScannerService.cs create mode 100644 Bimix.UI.Shared/Interfaces/IScannerService.cs create mode 100644 Bimix.UI.Shared/Services/NoOpScannerService.cs diff --git a/Bimix.UI.Mobile/Bimix.UI.Mobile.csproj b/Bimix.UI.Mobile/Bimix.UI.Mobile.csproj index bafaaea..c8fb4dc 100644 --- a/Bimix.UI.Mobile/Bimix.UI.Mobile.csproj +++ b/Bimix.UI.Mobile/Bimix.UI.Mobile.csproj @@ -2,6 +2,8 @@ net8.0-ios + true + @@ -21,15 +23,15 @@ enable - Bimix.UI.Mobile + Bimix - com.companyname.bimix.ui.mobile + cloud.bimit.bimix 1.0 1 - + 14.2 14.0 24.0 @@ -38,6 +40,14 @@ 6.5 + + cloud.bimit.bimix + Apple Development: Michal Zielinski (2F35ZHMBTB) + bimix-local + ios-arm64 + + + @@ -66,11 +76,12 @@ - + + - + \ No newline at end of file diff --git a/Bimix.UI.Mobile/MauiProgram.cs b/Bimix.UI.Mobile/MauiProgram.cs index b0d9168..97503c2 100644 --- a/Bimix.UI.Mobile/MauiProgram.cs +++ b/Bimix.UI.Mobile/MauiProgram.cs @@ -1,6 +1,9 @@ -using Bimix.UI.Shared.Extensions; +using Bimix.UI.Mobile.Services; +using Bimix.UI.Shared.Extensions; +using Bimix.UI.Shared.Interfaces; using Microsoft.Extensions.Logging; using MudBlazor.Services; +using ZXing.Net.Maui.Controls; namespace Bimix.UI.Mobile; @@ -11,12 +14,21 @@ public static class MauiProgram var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() + .UseBarcodeReader() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); builder.Services.AddMauiBlazorWebView(); - builder.Services.AddMudServices(); + if (DeviceInfo.Platform == DevicePlatform.iOS) + { + builder.Services.AddSingleton(); + } + else + { + builder.Services.AddSingleton(); + } + var baseUrl = GetApiBaseUrl(); builder.Services.AddSharedServices(baseUrl); diff --git a/Bimix.UI.Mobile/Services/ScannerService.cs b/Bimix.UI.Mobile/Services/ScannerService.cs new file mode 100644 index 0000000..290b36b --- /dev/null +++ b/Bimix.UI.Mobile/Services/ScannerService.cs @@ -0,0 +1,140 @@ +using Bimix.UI.Shared.Interfaces; +using ZXing.Net.Maui; +using ZXing.Net.Maui.Controls; + +namespace Bimix.UI.Mobile.Services; + +public class ScannerService : IScannerService +{ + public bool IsAvailable => true; + + public async Task ScanBarcodeAsync() + { + try + { + if (!IsAvailable) + return null; + + var hasPermission = await RequestCameraPermissionsAsync(); + if (!hasPermission) + return null; + + var tcs = new TaskCompletionSource(); + + await MainThread.InvokeOnMainThreadAsync(async () => + { + var scanner = new CameraBarcodeReaderView + { + Options = new BarcodeReaderOptions + { + Formats = BarcodeFormats.OneDimensional | BarcodeFormats.TwoDimensional, + AutoRotate = true, + Multiple = false + }, + HorizontalOptions = LayoutOptions.FillAndExpand, + VerticalOptions = LayoutOptions.FillAndExpand, + BackgroundColor = Colors.Black + }; + + scanner.BarcodesDetected += async (sender, e) => + { + if (e.Results?.Any() == true) + { + var barcode = e.Results.First(); + + // Wykonaj operacje UI na głównym wątku + await MainThread.InvokeOnMainThreadAsync(async () => + { + try + { + await Microsoft.Maui.Controls.Application.Current?.MainPage?.Navigation.PopModalAsync()!; + tcs.TrySetResult(barcode.Value); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error closing modal: {ex.Message}"); + tcs.TrySetException(ex); + } + }); + } + }; + + var cancelButton = new Button + { + Text = "Anuluj", + BackgroundColor = Colors.Red, + TextColor = Colors.White, + Margin = new Thickness(20), + HorizontalOptions = LayoutOptions.Center + }; + + cancelButton.Clicked += async (sender, e) => + { + try + { + await Microsoft.Maui.Controls.Application.Current?.MainPage?.Navigation.PopModalAsync()!; + tcs.TrySetResult(null); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error closing modal: {ex.Message}"); + tcs.TrySetException(ex); + } + }; + + var stackLayout = new StackLayout + { + Children = + { + new Label + { + Text = "Skieruj kamerę na kod kreskowy", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Start, + Margin = new Thickness(20), + FontSize = 18, + TextColor = Colors.White + }, + scanner, + cancelButton + }, + BackgroundColor = Colors.Black, + Spacing = 0 + }; + + var scannerPage = new ContentPage + { + Title = "Skanuj kod", + Content = stackLayout, + BackgroundColor = Colors.Black + }; + + await Microsoft.Maui.Controls.Application.Current?.MainPage?.Navigation.PushModalAsync(scannerPage)!; + }); + + var result = await tcs.Task; + System.Diagnostics.Debug.WriteLine($"Scanner returned: {result}"); + return result; + + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine($"Scanner error: {e.Message}"); + return null; + } + } + + public async Task RequestCameraPermissionsAsync() + { + try + { + var status = await Permissions.RequestAsync(); + return status == PermissionStatus.Granted; + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine($"Permission error: {e.Message}"); + return false; + } + } +} \ No newline at end of file diff --git a/Bimix.UI.Shared/Components/ProductListComponent.razor b/Bimix.UI.Shared/Components/ProductListComponent.razor index 44275ea..c2bdc13 100644 --- a/Bimix.UI.Shared/Components/ProductListComponent.razor +++ b/Bimix.UI.Shared/Components/ProductListComponent.razor @@ -36,13 +36,26 @@ - - +
+
+ +
+ @if (ScannerService.IsAvailable) + { + + } +
+
diff --git a/Bimix.UI.Shared/Components/ProductListComponent.razor.cs b/Bimix.UI.Shared/Components/ProductListComponent.razor.cs index cd1f5a8..6743de3 100644 --- a/Bimix.UI.Shared/Components/ProductListComponent.razor.cs +++ b/Bimix.UI.Shared/Components/ProductListComponent.razor.cs @@ -1,16 +1,19 @@ using Bimix.Application.DTOModels; using Bimix.Application.DTOModels.Common; -using Bimix.Domain.Entities; +using Bimix.UI.Shared.Interfaces; using Bimix.UI.Shared.Services; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.JSInterop; +using MudBlazor; + namespace Bimix.UI.Shared.Components; public partial class ProductListComponent : ComponentBase { [Inject] private ProductService ProductService { get; set; } = default!; + [Inject] private IScannerService ScannerService { get; set; } = default!; + [Inject] private ISnackbar Snackbar { get; set; } = default!; + private PagedResult products = new(); private ProductFilterRequest filterRequest = new(); @@ -20,11 +23,6 @@ public partial class ProductListComponent : ComponentBase { await LoadProducts(); } - - private async Task StartBarcodeScanner() - { - - } private async Task LoadProducts() { @@ -74,4 +72,34 @@ public partial class ProductListComponent : ComponentBase Console.WriteLine($"Usuń produkt: {productId}"); } + private string GetScannerIcon() + { + return ScannerService.IsAvailable ? Icons.Material.Filled.CameraAlt : ""; + } + + private async Task OnScannerClick() + { + if (!ScannerService.IsAvailable) + { + Snackbar.Add("Skaner nie jest dostępny na tej platformie", Severity.Warning); + return; + } + + try + { + var scannedCode = await ScannerService.ScanBarcodeAsync(); + if (!string.IsNullOrEmpty(scannedCode)) + { + filterRequest.Ean = scannedCode; + await SearchProducts(); + Snackbar.Add($"Zeskanowano kod: {scannedCode}", Severity.Success); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Scanner error: {ex.Message}"); + Snackbar.Add("Błąd podczas skanowania", Severity.Error); + } + } + } \ No newline at end of file diff --git a/Bimix.UI.Shared/Interfaces/IScannerService.cs b/Bimix.UI.Shared/Interfaces/IScannerService.cs new file mode 100644 index 0000000..fdc7454 --- /dev/null +++ b/Bimix.UI.Shared/Interfaces/IScannerService.cs @@ -0,0 +1,9 @@ +namespace Bimix.UI.Shared.Interfaces; + +public interface IScannerService +{ + bool IsAvailable { get; } + Task RequestCameraPermissionsAsync(); + Task ScanBarcodeAsync(); + +} \ No newline at end of file diff --git a/Bimix.UI.Shared/Services/NoOpScannerService.cs b/Bimix.UI.Shared/Services/NoOpScannerService.cs new file mode 100644 index 0000000..5467890 --- /dev/null +++ b/Bimix.UI.Shared/Services/NoOpScannerService.cs @@ -0,0 +1,16 @@ +using Bimix.UI.Shared.Interfaces; + +public class NoOpScannerService : IScannerService +{ + public bool IsAvailable => false; + public Task ScanBarcodeAsync() + { + return Task.FromResult(null); + } + public Task RequestCameraPermissionsAsync() + { + return Task.FromResult(false); + } + + +} \ No newline at end of file diff --git a/Bimix.UI.Web/Program.cs b/Bimix.UI.Web/Program.cs index 7b4649e..7636888 100644 --- a/Bimix.UI.Web/Program.cs +++ b/Bimix.UI.Web/Program.cs @@ -1,5 +1,6 @@ using Bimix.UI.Shared; -using Bimix.UI.Shared.Extensions; +using Bimix.UI.Shared.Extensions; +using Bimix.UI.Shared.Interfaces; using Bimix.UI.Web.Components; using MudBlazor.Services; @@ -11,6 +12,8 @@ builder.Services.AddMudServices(); builder.Services.AddSharedServices("http://localhost:7142"); +builder.Services.AddSingleton(); + var app = builder.Build();