3 Commits

Author SHA1 Message Date
833a1e456f Iniziata implementazione notifiche firebase 2025-08-25 10:00:41 +02:00
9957229e70 Vario 2025-08-21 10:51:32 +02:00
cd88c79b32 Finish v1.1.0 2025-08-07 09:28:06 +02:00
21 changed files with 324 additions and 7 deletions

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto;
public class RegisterDeviceDTO
{
[JsonPropertyName("userDeviceToken")]
public WtbUserDeviceTokenDTO UserDeviceToken { get; set; }
}

View File

@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto;
public class WtbUserDeviceTokenDTO
{
[JsonPropertyName("type")]
public string Type => "wtb_user_device_tokens";
[JsonPropertyName("deviceToken")]
public string DeviceToken { get; set; }
[JsonPropertyName("userName")]
public string Username { get; set; }
[JsonPropertyName("appName")]
public int AppName => 7; //salesbook
[JsonPropertyName("platform")]
public string Platform { get; set; }
}

View File

@@ -0,0 +1,38 @@
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account;
using IntegryApiClient.Core.Domain.RestClient.Contacts;
using Microsoft.Extensions.Logging;
using salesbook.Maui.Core.RestClient.IntegryApi.Dto;
using salesbook.Shared.Core.Interface;
namespace salesbook.Maui.Core.RestClient.IntegryApi;
public class IntegryNotificationRestClient(
ILogger<IntegryNotificationRestClient> logger,
IUserSession userSession,
IIntegryApiRestClient integryApiRestClient
) : IIntegryNotificationRestClient
{
public async Task Register(string fcmToken, ILogger? logger1 = null)
{
logger1 ??= logger;
var userDeviceToken = new RegisterDeviceDTO()
{
UserDeviceToken = new WtbUserDeviceTokenDTO()
{
DeviceToken = fcmToken,
Platform = OperatingSystem.IsAndroid() ? "Android" : "iOS",
Username = userSession.User.Username
}
};
try
{
await integryApiRestClient.AuthorizedPost<object>($"device_tokens/insert", userDeviceToken,
logger: logger1);
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
}
}
}

View File

@@ -1,6 +1,6 @@
using salesbook.Shared.Core.Interface;
namespace salesbook.Maui.Core.Services;
namespace salesbook.Maui.Core.System.Network;
public class NetworkService : INetworkService
{

View File

@@ -0,0 +1,16 @@
using salesbook.Shared.Core.Interface;
using Shiny;
using Shiny.Push;
namespace salesbook.Maui.Core.System.Notification;
public class FirebaseNotificationService(IPushManager pushManager, IIntegryNotificationRestClient integryNotificationRestClient) : IFirebaseNotificationService
{
public async Task InitFirebase()
{
var (accessState, token) = await pushManager.RequestAccess();
if (accessState == AccessState.Denied || token is null) return;
await integryNotificationRestClient.Register(token);
}
}

View File

@@ -0,0 +1,31 @@
using Shiny.Push;
namespace salesbook.Maui.Core.System.Notification.Push;
public class PushNotificationDelegate : IPushDelegate
{
public Task OnEntry(PushNotification notification)
{
// fires when the user taps on a push notification
return Task.CompletedTask;
}
public Task OnReceived(PushNotification notification)
{
// fires when a push notification is received (silient or notification)
//notification.Data["content-available"] = "1";
return Task.CompletedTask;
}
public Task OnNewToken(string token)
{
// fires when a push notification change is set by the operating system or provider
return Task.CompletedTask;
}
public Task OnUnRegistered(string token)
{
// fires when a push notification change is set by the operating system or provider
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyC_QtQpsVortjzgl-B7__IQZ-85lOct55E</string>
<key>GCM_SENDER_ID</key>
<string>830771692001</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>it.integry.salesbook</string>
<key>PROJECT_ID</key>
<string>salesbook-smetar</string>
<key>STORAGE_BUCKET</key>
<string>salesbook-smetar.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:830771692001:ios:59d8b1d8570ac81f3752a0</string>
</dict>
</plist>

View File

@@ -6,7 +6,11 @@ using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using MudBlazor.Services;
using MudExtensions.Services;
using salesbook.Maui.Core.RestClient.IntegryApi;
using salesbook.Maui.Core.Services;
using salesbook.Maui.Core.System.Network;
using salesbook.Maui.Core.System.Notification;
using salesbook.Maui.Core.System.Notification.Push;
using salesbook.Shared;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Helpers;
@@ -16,6 +20,7 @@ using salesbook.Shared.Core.Messages.Activity.New;
using salesbook.Shared.Core.Messages.Back;
using salesbook.Shared.Core.Messages.Contact;
using salesbook.Shared.Core.Services;
using Shiny;
namespace salesbook.Maui
{
@@ -64,6 +69,12 @@ namespace salesbook.Maui
builder.Services.AddScoped<BackNavigationService>();
builder.Services.AddScoped<CopyActivityService>();
builder.Services.AddScoped<NewContactService>();
//Notification
builder.Services.AddNotifications();
builder.Services.AddPush<PushNotificationDelegate>();
builder.Services.AddSingleton<IIntegryNotificationRestClient, IntegryNotificationRestClient>();
builder.Services.AddSingleton<IFirebaseNotificationService, FirebaseNotificationService>();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();

View File

@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:usesCleartextTraffic="true" android:supportsRtl="true"></application>
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:usesCleartextTraffic="true" android:supportsRtl="true">
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

View File

@@ -7,6 +7,12 @@ namespace salesbook.Maui
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
[IntentFilter([Shiny.ShinyPushIntents.NotificationClickAction],
Categories = new[]
{
"android.intent.category.DEFAULT"
}
)]
public class MainActivity : MauiAppCompatActivity
{
}

View File

@@ -45,5 +45,9 @@
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Permette all'app di salvare file o immagini nella tua libreria fotografica se necessario.</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "830771692001",
"project_id": "salesbook-smetar",
"storage_bucket": "salesbook-smetar.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:830771692001:android:06bc5a9706bc9bef3752a0",
"android_client_info": {
"package_name": "it.integry.salesbook"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB43ai_Ph0phO_OkBC1wAOazKZUV9KsLaM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@@ -93,6 +93,16 @@
-->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0-android'">
<GoogleServicesJson Include="google-services.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</GoogleServicesJson>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0-ios'">
<BundleResource Include="GoogleService-Info.plist" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<!-- Android App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" ForegroundScale="0.65" />
@@ -128,6 +138,8 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.81" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.6" />
<PackageReference Include="Sentry.Maui" Version="5.11.2" />
<PackageReference Include="Shiny.Notifications" Version="3.3.4" />
<PackageReference Include="Shiny.Push" Version="3.3.4" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
</ItemGroup>

View File

@@ -4,6 +4,7 @@
@using salesbook.Shared.Components.Layout.Spinner
@inject IFormFactor FormFactor
@inject INetworkService NetworkService
@inject IFirebaseNotificationService FirebaseNotificationService
<SpinnerLayout FullScreen="true" />
@@ -11,6 +12,15 @@
{
protected override async Task OnInitializedAsync()
{
try
{
await FirebaseNotificationService.InitFirebase();
}
catch (Exception e)
{
Console.WriteLine($"Firebase init: {e.Message}");
}
var lastSyncDate = LocalStorage.Get<DateTime>("last-sync");
if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate.Equals(DateTime.MinValue))

View File

@@ -1,7 +1,6 @@
@using System.Globalization
@using System.Text.RegularExpressions
@using CommunityToolkit.Mvvm.Messaging
@using Java.Util.Jar
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Core.Entity
@@ -126,13 +125,13 @@
{
@if (item.p.Type == AttachedDTO.TypeAttached.Position)
{
<MudChip T="string" Icon="@Icons.Material.Rounded.LocationOn" Color="Color.Success" OnClose="() => OnRemoveAttached(item.index)">
<MudChip T="string" Icon="@Icons.Material.Rounded.LocationOn" Color="Color.Success" OnClick="() => OpenPosition(item.p)" OnClose="() => OnRemoveAttached(item.index)">
@item.p.Description
</MudChip>
}
else
{
<MudChip T="string" Color="Color.Default" OnClose="() => OnRemoveAttached(item.index)">
<MudChip T="string" Color="Color.Default" OnClick="() => OpenAttached(item.p)" OnClose="() => OnRemoveAttached(item.index)">
@item.p.Name
</MudChip>
}
@@ -518,7 +517,7 @@
if (resultNamePosition is true)
attached.Description = NamePosition;
attached.Name = NamePosition!;
attached.Name = NamePosition!;
}
AttachedList ??= [];
@@ -542,4 +541,37 @@
StateHasChanged();
}
private async Task OpenAttached(AttachedDTO attached)
{
if (attached is { FileContent: not null, MimeType: not null })
{
var fileViewerUrl = $"data:{attached.MimeType};base64,{Convert.ToBase64String(attached.FileContent)}";
await ModalHelpers.OpenViewAttach(Dialog, fileViewerUrl);
}
else
{
Snackbar.Clear();
Snackbar.Add("Impossibile aprire il file", Severity.Error);
}
}
private void OpenPosition(AttachedDTO attached)
{
if (attached is { Lat: not null, Lng: not null })
{
const string baseUrl = "https://www.google.it/maps/place/";
NavigationManager.NavigateTo(
$"{baseUrl}{AdjustCoordinate(attached.Lat.Value)},{AdjustCoordinate(attached.Lng.Value)}"
);
}
else
{
Snackbar.Clear();
Snackbar.Add("Impossibile aprire la posizione", Severity.Error);
}
}
private static string AdjustCoordinate(double coordinate) =>
coordinate.ToString(CultureInfo.InvariantCulture).Replace(",", ".");
}

View File

@@ -559,6 +559,9 @@
private async Task ConvertProspectToContact()
{
await IntegryApiService.TransferProspect(new CRMTransferProspectRequestDTO
{
CodPpro = ContactModel.CodContact
});
}
}

View File

@@ -0,0 +1,14 @@
<MudDialog Class="customDialog-form">
<DialogContent>
@if (!string.IsNullOrEmpty(FileViewerUrl))
{
<iframe src="@FileViewerUrl" style="width:100%; height:80vh; border:none;"></iframe>
}
</DialogContent>
</MudDialog>
@code {
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; }
[Parameter] public string? FileViewerUrl { get; set; }
}

View File

@@ -0,0 +1,7 @@
.content.attached {
display: flex;
flex-direction: column;
gap: 2rem;
padding: 1rem;
height: unset;
}

View File

@@ -85,4 +85,23 @@ public class ModalHelpers
return await modal.Result;
}
public static async Task<DialogResult?> OpenViewAttach(IDialogService dialog, string? fileViewUrl)
{
var modal = await dialog.ShowAsync<ViewAttached>(
"View attached",
new DialogParameters<ViewAttached>
{
{ x => x.FileViewerUrl, fileViewUrl }
},
new DialogOptions
{
FullScreen = true,
CloseButton = true,
NoHeader = true
}
);
return await modal.Result;
}
}

View File

@@ -0,0 +1,6 @@
namespace salesbook.Shared.Core.Interface;
public interface IFirebaseNotificationService
{
Task InitFirebase();
}

View File

@@ -0,0 +1,8 @@
using Microsoft.Extensions.Logging;
namespace salesbook.Shared.Core.Interface;
public interface IIntegryNotificationRestClient
{
Task Register(string fcmToken, ILogger? logger1 = null);
}