Scan ean code on iOS app

This commit is contained in:
Michał Zieliński
2025-07-17 19:17:27 +02:00
parent 2a42f16daf
commit b673fd2da3
8 changed files with 255 additions and 23 deletions

View File

@@ -2,6 +2,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-ios</TargetFramework> <TargetFramework>net8.0-ios</TargetFramework>
<GenerateXcodeProject>true</GenerateXcodeProject>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
@@ -21,10 +23,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Display name --> <!-- Display name -->
<ApplicationTitle>Bimix.UI.Mobile</ApplicationTitle> <ApplicationTitle>Bimix</ApplicationTitle>
<!-- App Identifier --> <!-- App Identifier -->
<ApplicationId>com.companyname.bimix.ui.mobile</ApplicationId> <ApplicationId>cloud.bimit.bimix</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
@@ -38,6 +40,14 @@
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Platform)' == 'iPhone'">
<ApplicationId>cloud.bimit.bimix</ApplicationId>
<CodesignKey>Apple Development: Michal Zielinski (2F35ZHMBTB)</CodesignKey>
<CodesignProvision>bimix-local</CodesignProvision>
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<!-- App Icon --> <!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/> <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
@@ -66,7 +76,8 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)"/> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)"/>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1"/> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1"/>
<PackageReference Include="MudBlazor" Version="8.8.0"/> <PackageReference Include="MudBlazor" Version="8.8.0"/>
<PackageReference Include="ZXing.Net.MAUI" Version="0.4.0" />
<PackageReference Include="ZXing.Net.MAUI.Controls" Version="0.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -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 Microsoft.Extensions.Logging;
using MudBlazor.Services; using MudBlazor.Services;
using ZXing.Net.Maui.Controls;
namespace Bimix.UI.Mobile; namespace Bimix.UI.Mobile;
@@ -11,12 +14,21 @@ public static class MauiProgram
var builder = MauiApp.CreateBuilder(); var builder = MauiApp.CreateBuilder();
builder builder
.UseMauiApp<App>() .UseMauiApp<App>()
.UseBarcodeReader()
.ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); });
builder.Services.AddMauiBlazorWebView(); builder.Services.AddMauiBlazorWebView();
builder.Services.AddMudServices(); builder.Services.AddMudServices();
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
builder.Services.AddSingleton<IScannerService, ScannerService>();
}
else
{
builder.Services.AddSingleton<IScannerService, NoOpScannerService>();
}
var baseUrl = GetApiBaseUrl(); var baseUrl = GetApiBaseUrl();
builder.Services.AddSharedServices(baseUrl); builder.Services.AddSharedServices(baseUrl);

View File

@@ -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<string?> ScanBarcodeAsync()
{
try
{
if (!IsAvailable)
return null;
var hasPermission = await RequestCameraPermissionsAsync();
if (!hasPermission)
return null;
var tcs = new TaskCompletionSource<string?>();
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<bool> RequestCameraPermissionsAsync()
{
try
{
var status = await Permissions.RequestAsync<Permissions.Camera>();
return status == PermissionStatus.Granted;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine($"Permission error: {e.Message}");
return false;
}
}
}

View File

@@ -36,13 +36,26 @@
</MudItem> </MudItem>
<MudItem xs="12" sm="6" md="4"> <MudItem xs="12" sm="6" md="4">
<MudTextField @bind-Value="filterRequest.Ean" <div style="display: flex; gap: 8px; align-items: flex-end;">
Label="EAN" <div style="flex: 1;">
Immediate="true" <MudTextField @bind-Value="filterRequest.Ean"
DebounceInterval="500" Label="EAN"
OnDebounceIntervalElapsed="SearchProducts" Immediate="true"
Clearable="true"> DebounceInterval="500"
</MudTextField> OnDebounceIntervalElapsed="SearchProducts"
Clearable="true"/>
</div>
@if (ScannerService.IsAvailable)
{
<MudIconButton Icon="@Icons.Material.Filled.CameraAlt"
Color="Color.Primary"
OnClick="OnScannerClick"
Variant="Variant.Filled"
Size="Size.Medium"
Title="Skanuj kod EAN"/>
}
</div>
</MudItem> </MudItem>
<MudItem xs="12" sm="6" md="4"> <MudItem xs="12" sm="6" md="4">

View File

@@ -1,16 +1,19 @@
using Bimix.Application.DTOModels; using Bimix.Application.DTOModels;
using Bimix.Application.DTOModels.Common; using Bimix.Application.DTOModels.Common;
using Bimix.Domain.Entities; using Bimix.UI.Shared.Interfaces;
using Bimix.UI.Shared.Services; using Bimix.UI.Shared.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using MudBlazor;
using Microsoft.JSInterop;
namespace Bimix.UI.Shared.Components; namespace Bimix.UI.Shared.Components;
public partial class ProductListComponent : ComponentBase public partial class ProductListComponent : ComponentBase
{ {
[Inject] private ProductService ProductService { get; set; } = default!; [Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private IScannerService ScannerService { get; set; } = default!;
[Inject] private ISnackbar Snackbar { get; set; } = default!;
private PagedResult<ProductDto> products = new(); private PagedResult<ProductDto> products = new();
private ProductFilterRequest filterRequest = new(); private ProductFilterRequest filterRequest = new();
@@ -21,11 +24,6 @@ public partial class ProductListComponent : ComponentBase
await LoadProducts(); await LoadProducts();
} }
private async Task StartBarcodeScanner()
{
}
private async Task LoadProducts() private async Task LoadProducts()
{ {
isLoading = true; isLoading = true;
@@ -74,4 +72,34 @@ public partial class ProductListComponent : ComponentBase
Console.WriteLine($"Usuń produkt: {productId}"); 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);
}
}
} }

View File

@@ -0,0 +1,9 @@
namespace Bimix.UI.Shared.Interfaces;
public interface IScannerService
{
bool IsAvailable { get; }
Task<bool> RequestCameraPermissionsAsync();
Task<string?> ScanBarcodeAsync();
}

View File

@@ -0,0 +1,16 @@
using Bimix.UI.Shared.Interfaces;
public class NoOpScannerService : IScannerService
{
public bool IsAvailable => false;
public Task<string?> ScanBarcodeAsync()
{
return Task.FromResult<string?>(null);
}
public Task<bool> RequestCameraPermissionsAsync()
{
return Task.FromResult(false);
}
}

View File

@@ -1,5 +1,6 @@
using Bimix.UI.Shared; 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 Bimix.UI.Web.Components;
using MudBlazor.Services; using MudBlazor.Services;
@@ -11,6 +12,8 @@ builder.Services.AddMudServices();
builder.Services.AddSharedServices("http://localhost:7142"); builder.Services.AddSharedServices("http://localhost:7142");
builder.Services.AddSingleton<IScannerService, NoOpScannerService>();
var app = builder.Build(); var app = builder.Build();