9 Commits
Author SHA1 Message Date
MarcoEandClaude Opus 5.5 495f335b72 Evita articoli persi o doppi con griglia e scanner insieme
Nella scheda, scegliendo alcuni articoli dalla griglia e sparandone altri,
non tutti i barcode arrivavano al salvataggio.

- La selezione dalla griglia modificava Scheda.Articoli da un thread del
  pool, mentre le scansioni lo fanno sul thread della UI: le due modifiche
  potevano sovrapporsi e far perdere articoli. Ora passa da InvokeAsync.
- La griglia non scartava i barcode gia' sparati, e lo scanner controllava
  solo il codice letto, non quello restituito dal server (che puo' essere
  il barcode principale, lo stesso della griglia). Un doppione rompe il
  @key della lista e il salvataggio locale (indice unico SchedaId+Barcode).
  Ora entrambi i percorsi aggiungono solo barcode non ancora presenti.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:40:20 +01:00
MarcoEandClaude Opus 5.5 ed7c28feac Aggiorna IntegryApiClient alla 2.6.0
Con la 2.0.1 il login poteva fallire con WrongAccessTokenJsonException:
il token del server non contiene piu' la proprieta' 'web' che la vecchia
versione considerava obbligatoria.

- IntegryApiClient.Core, .MAUI e .Blazor: 2.0.1 -> 2.6.0.
- La 2.6.0 richiede dipendenze piu' recenti, che il restore segnalava come
  downgrade (NU1605): System.IdentityModel.Tokens.Jwt 8.17.0 -> 8.18.0,
  Microsoft.Extensions.Logging e .Logging.Debug 10.0.7 -> 10.0.8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:40:20 +01:00
MarcoE f59c8d3aaf Finish v1.2.3(9) 2026-10-21 08:51:50 +02:00
MarcoE 42e2403362 Finish v1.2.3(9) 2026-10-21 08:51:50 +02:00
MarcoE 249b301920 -> 1.2.3 (9) 2026-10-21 08:51:43 +02:00
MarcoEandClaude Opus 5.5 885b3cc6b6 Avvisa quando manca il permesso della fotocamera
Con il permesso negato "Aggiungi una foto" non faceva nulla: il servizio
restituiva null come per un annullamento. Dopo un rifiuto definitivo
Android non ripropone la richiesta, quindi il rilevatore restava bloccato
senza capire perche'.

- AttachedService: la richiesta dei permessi diventa RequestCameraPermissions,
  usata anche da SelectImageFromCamera; OpenAppSettings apre le impostazioni
  dell'app.
- ModalFormScheda e ModalAddAttached: chiedono i permessi prima di scattare
  e, se negati, mostrano un avviso con l'azione "Impostazioni".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 08:49:34 +02:00
MarcoEandClaude Opus 5.5 7e1492d118 Avvisa quando la fotocamera non si apre e passa alla galleria
Se lo scatto falliva, AttachedService restituiva null come per un
annullamento: il rilevatore toccava "Aggiungi una foto" e non succedeva
nulla.

- AttachedService: espone IsCameraAvailable (IsCaptureSupported) e, dopo
  aver registrato l'errore e averlo inviato a Sentry, lo rilancia invece di
  restituire null.
- ModalFormScheda: dalla scheda il pulsante apre solo la fotocamera, quindi
  senza fotocamera non c'era alternativa. Ora avvisa e apre la galleria;
  se lo scatto fallisce mostra un errore.
- ModalAddAttached: stessa gestione dell'errore di scatto.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 08:45:57 +02:00
MarcoEandClaude Opus 5.5 fd0c218ab5 Dichiara la fotocamera di sistema nelle queries del manifest
Da Android 11 un'app vede le altre app installate solo se le dichiara in
<queries>. Senza l'intent IMAGE_CAPTURE, MediaPicker.CapturePhotoAsync non
trovava la fotocamera di sistema e falliva con "Either there was no camera
on the device or 'android.media.action.IMAGE_CAPTURE' was not added to the
<queries> element" (segnalato su Sentry).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 08:45:57 +02:00
MarcoE e1ca03f646 Finish v1.2.2 2026-09-29 10:34:45 +02:00
8 changed files with 142 additions and 32 deletions

No files matched your search

+15 -3
View File
@@ -8,12 +8,21 @@ namespace SteUp.Maui.Core.Services;
public class AttachedService(ILogger<FileManager> logger) : IAttachedService public class AttachedService(ILogger<FileManager> logger) : IAttachedService
{ {
public async Task<AttachedDto?> SelectImageFromCamera() public bool IsCameraAvailable => MediaPicker.Default.IsCaptureSupported;
public async Task<bool> RequestCameraPermissions()
{ {
var cameraPerm = await Permissions.RequestAsync<Permissions.Camera>(); var cameraPerm = await Permissions.RequestAsync<Permissions.Camera>();
var storagePerm = await Permissions.RequestAsync<Permissions.StorageWrite>(); var storagePerm = await Permissions.RequestAsync<Permissions.StorageWrite>();
if (cameraPerm != PermissionStatus.Granted || storagePerm != PermissionStatus.Granted) return cameraPerm == PermissionStatus.Granted && storagePerm == PermissionStatus.Granted;
}
public void OpenAppSettings() => AppInfo.Current.ShowSettingsUI();
public async Task<AttachedDto?> SelectImageFromCamera()
{
if (!await RequestCameraPermissions())
return null; return null;
FileResult? result; FileResult? result;
@@ -28,7 +37,10 @@ public class AttachedService(ILogger<FileManager> logger) : IAttachedService
logger.LogError(ex, ex.Message); logger.LogError(ex, ex.Message);
Console.WriteLine($"Errore cattura foto: {ex.Message}"); Console.WriteLine($"Errore cattura foto: {ex.Message}");
SentrySdk.CaptureException(ex); SentrySdk.CaptureException(ex);
return null;
// Rilancia: restituire null qui era indistinguibile da un annullamento,
// e il rilevatore toccava "Aggiungi una foto" senza vedere nulla.
throw;
} }
return result is null ? null : await UtilityFile.ConvertToDto(result, AttachedDto.TypeAttached.Image); return result is null ? null : await UtilityFile.ConvertToDto(result, AttachedDto.TypeAttached.Image);
@@ -25,4 +25,11 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- Android 11+: rende visibile l'app fotocamera di sistema a MediaPicker -->
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
</manifest> </manifest>
+5 -5
View File
@@ -30,8 +30,8 @@
<ApplicationId>it.integry.SteUp</ApplicationId> <ApplicationId>it.integry.SteUp</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>1.2.2</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.2.3</ApplicationDisplayVersion>
<ApplicationVersion>8</ApplicationVersion> <ApplicationVersion>9</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<!-- <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">--> <!-- <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">-->
@@ -116,12 +116,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="14.1.0" /> <PackageReference Include="CommunityToolkit.Maui" Version="14.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="IntegryApiClient.MAUI" Version="2.0.1" /> <PackageReference Include="IntegryApiClient.MAUI" Version="2.6.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageReference Include="Microsoft.Maui.Controls" Version="10.0.51" /> <PackageReference Include="Microsoft.Maui.Controls" Version="10.0.51" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.51" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.51" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.51" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.51" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.8" />
<PackageReference Include="Sentry.Maui" Version="6.4.1" /> <PackageReference Include="Sentry.Maui" Version="6.4.1" />
<PackageReference Include="SkiaSharp" Version="3.119.2" /> <PackageReference Include="SkiaSharp" Version="3.119.2" />
</ItemGroup> </ItemGroup>
@@ -40,7 +40,32 @@
private async Task OnCamera() private async Task OnCamera()
{ {
var selectImageFromCamera = await AttachedService.SelectImageFromCamera(); if (!await AttachedService.RequestCameraPermissions())
{
Snackbar.Clear();
Snackbar.Add("Per scattare le foto serve il permesso della fotocamera", Severity.Warning, config =>
{
config.Action = "Impostazioni";
config.OnClick = _ =>
{
AttachedService.OpenAppSettings();
return Task.CompletedTask;
};
});
return;
}
AttachedDto? selectImageFromCamera;
try
{
selectImageFromCamera = await AttachedService.SelectImageFromCamera();
}
catch (Exception)
{
Snackbar.Clear();
Snackbar.Add("Non è stato possibile scattare la foto, riprova", Severity.Error);
return;
}
if (selectImageFromCamera != null) if (selectImageFromCamera != null)
{ {
@@ -754,12 +754,60 @@
#region File #region File
// Dopo un rifiuto definitivo Android non ripropone la richiesta: il
// permesso si riattiva solo dalle impostazioni, quindi le apriamo noi.
private void ShowCameraPermissionDenied()
{
Snackbar.Clear();
Snackbar.Add("Per scattare le foto serve il permesso della fotocamera", Severity.Warning, config =>
{
config.Action = "Impostazioni";
config.OnClick = _ =>
{
AttachedService.OpenAppSettings();
return Task.CompletedTask;
};
});
}
private async Task OpenAddAttached() private async Task OpenAddAttached()
{ {
var selectImageFromCamera = await AttachedService.SelectImageFromCamera(); List<AttachedDto>? attachedList;
if (selectImageFromCamera == null) return;
var attachedList = new List<AttachedDto> { selectImageFromCamera }; if (AttachedService.IsCameraAvailable)
{
if (!await AttachedService.RequestCameraPermissions())
{
ShowCameraPermissionDenied();
return;
}
AttachedDto? selectImageFromCamera;
try
{
selectImageFromCamera = await AttachedService.SelectImageFromCamera();
}
catch (Exception)
{
// Gia' registrato e inviato a Sentry dal servizio
Snackbar.Clear();
Snackbar.Add("Non è stato possibile scattare la foto, riprova", Severity.Error);
return;
}
if (selectImageFromCamera == null) return;
attachedList = [selectImageFromCamera];
}
else
{
// Senza fotocamera "Aggiungi una foto" non avrebbe altra strada:
// si passa alla galleria invece di lasciare il rilevatore fermo.
Snackbar.Clear();
Snackbar.Add("Fotocamera non disponibile: scegli la foto dalla galleria", Severity.Warning);
attachedList = await AttachedService.SelectImageFromGallery();
if (attachedList is null || attachedList.Count == 0) return;
}
OnLoading = true; OnLoading = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@@ -848,29 +896,32 @@
var modal = await WithChildDialog(() => Dialog.OpenSelectArt(articoli)); var modal = await WithChildDialog(() => Dialog.OpenSelectArt(articoli));
// Gli articoli si aggiungono sul thread della UI, come fanno le scansioni:
// toccare Scheda.Articoli da qui mentre arriva una lettura (o mentre la
// lista viene disegnata) puo' far perdere articoli.
await InvokeAsync(() => await InvokeAsync(() =>
{ {
OnLoading = false; OnLoading = false;
TextLoading = null; TextLoading = null;
if (modal is { Canceled: false, Data: List<ArticoliInGrigliaDto> articoliSelezionati })
{
// Quelli gia' sparati non si aggiungono di nuovo.
Scheda.Articoli.InsertRange(0, articoliSelezionati
.DistinctBy(x => x.Barcode)
.Where(x => !ContainsBarcode(x.Barcode))
.Select(x => new SchedaArticolo
{
Barcode = x.Barcode,
Descrizione = x.Descrizione
})
);
RecalcDirty();
}
StateHasChanged(); StateHasChanged();
}); });
if (modal is { Canceled: false, Data: List<ArticoliInGrigliaDto> articoliSelezionati })
{
Scheda.Articoli.InsertRange(0, articoliSelezionati.ConvertAll(x => new SchedaArticolo
{
Barcode = x.Barcode,
Descrizione = x.Descrizione
})
);
await InvokeAsync(() =>
{
RecalcDirty();
StateHasChanged();
});
}
}); });
} }
@@ -936,7 +987,7 @@
try try
{ {
if (value is null) return; if (value is null) return;
if (Scheda.Articoli.Any(x => x.Barcode.Equals(value))) return; if (ContainsBarcode(value)) return;
await InvokeAsync(() => await InvokeAsync(() =>
{ {
@@ -951,6 +1002,11 @@
OnLoading = false; OnLoading = false;
StateHasChanged(); StateHasChanged();
// Il controllo va ripetuto sul barcode restituito: il server puo' rispondere
// con un barcode diverso da quello letto (lo stesso gia' scelto dalla griglia),
// e due letture ravvicinate superano entrambe il controllo iniziale.
if (art != null && ContainsBarcode(art.Barcode)) return;
if (art != null) if (art != null)
{ {
Scheda.Articoli.Insert(0, new SchedaArticolo Scheda.Articoli.Insert(0, new SchedaArticolo
@@ -984,6 +1040,8 @@
private void OnErrorScan(string? value) => OnError(new Exception(value), value); private void OnErrorScan(string? value) => OnError(new Exception(value), value);
private bool ContainsBarcode(string barcode) => Scheda.Articoli.Any(x => x.Barcode.Equals(barcode));
#endregion #endregion
private void OnError(Exception? e, string? errorMessage) private void OnError(Exception? e, string? errorMessage)
@@ -4,6 +4,14 @@ namespace SteUp.Shared.Core.Interface.System;
public interface IAttachedService public interface IAttachedService
{ {
bool IsCameraAvailable { get; }
/// <summary>Chiede i permessi per scattare; false se negati.</summary>
Task<bool> RequestCameraPermissions();
void OpenAppSettings();
/// <summary>Null se l'utente annulla; lancia se lo scatto fallisce.</summary>
Task<AttachedDto?> SelectImageFromCamera(); Task<AttachedDto?> SelectImageFromCamera();
Task<List<AttachedDto>?> SelectImageFromGallery(); Task<List<AttachedDto>?> SelectImageFromGallery();
} }
+2 -2
View File
@@ -11,13 +11,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IntegryApiClient.Core" Version="2.0.1" /> <PackageReference Include="IntegryApiClient.Core" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.7" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="MudBlazor" Version="9.0.0" /> <PackageReference Include="MudBlazor" Version="9.0.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="9.0.1" /> <PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="9.0.1" />
<PackageReference Include="Sentry" Version="6.4.1" /> <PackageReference Include="Sentry" Version="6.4.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.7" />
<PackageReference Include="Microsoft.Maui.Essentials" Version="10.0.51" /> <PackageReference Include="Microsoft.Maui.Essentials" Version="10.0.51" />
</ItemGroup> </ItemGroup>
+1 -1
View File
@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IntegryApiClient.Blazor" Version="2.0.1" /> <PackageReference Include="IntegryApiClient.Blazor" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.7" />