generated from Integry/Template_NetMauiBlazorHybrid
Creata card notifiche
This commit is contained in:
@@ -1,8 +1,15 @@
|
|||||||
using Shiny.Push;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using salesbook.Shared.Core.Dto.Notification;
|
||||||
|
using salesbook.Shared.Core.Interface.IntegryApi;
|
||||||
|
using salesbook.Shared.Core.Messages.Notification;
|
||||||
|
using Shiny.Push;
|
||||||
|
|
||||||
namespace salesbook.Maui.Core.System.Notification.Push;
|
namespace salesbook.Maui.Core.System.Notification.Push;
|
||||||
|
|
||||||
public class PushNotificationDelegate : IPushDelegate
|
public class PushNotificationDelegate(
|
||||||
|
IIntegryNotificationRestClient integryNotificationRestClient,
|
||||||
|
IMessenger messenger
|
||||||
|
) : IPushDelegate
|
||||||
{
|
{
|
||||||
public Task OnEntry(PushNotification notification)
|
public Task OnEntry(PushNotification notification)
|
||||||
{
|
{
|
||||||
@@ -12,14 +19,21 @@ public class PushNotificationDelegate : IPushDelegate
|
|||||||
|
|
||||||
public Task OnReceived(PushNotification notification)
|
public Task OnReceived(PushNotification notification)
|
||||||
{
|
{
|
||||||
// fires when a push notification is received (silient or notification)
|
if (notification.Notification is null) return Task.CompletedTask;
|
||||||
//notification.Data["content-available"] = "1";
|
var pushNotification = new PushNotificationDTO
|
||||||
|
{
|
||||||
|
Title = notification.Notification.Title,
|
||||||
|
Message = notification.Notification.Message
|
||||||
|
};
|
||||||
|
|
||||||
|
messenger.Send(new NewPushNotificationMessage(pushNotification));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnNewToken(string token)
|
public Task OnNewToken(string token)
|
||||||
{
|
{
|
||||||
// fires when a push notification change is set by the operating system or provider
|
integryNotificationRestClient.Register(token);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using salesbook.Shared.Core.Messages.Activity.Copy;
|
|||||||
using salesbook.Shared.Core.Messages.Activity.New;
|
using salesbook.Shared.Core.Messages.Activity.New;
|
||||||
using salesbook.Shared.Core.Messages.Back;
|
using salesbook.Shared.Core.Messages.Back;
|
||||||
using salesbook.Shared.Core.Messages.Contact;
|
using salesbook.Shared.Core.Messages.Contact;
|
||||||
|
using salesbook.Shared.Core.Messages.Notification;
|
||||||
using salesbook.Shared.Core.Services;
|
using salesbook.Shared.Core.Services;
|
||||||
using Shiny;
|
using Shiny;
|
||||||
|
|
||||||
@@ -71,14 +72,16 @@ namespace salesbook.Maui
|
|||||||
builder.Services.AddSingleton<JobSteps>();
|
builder.Services.AddSingleton<JobSteps>();
|
||||||
builder.Services.AddSingleton<UserPageState>();
|
builder.Services.AddSingleton<UserPageState>();
|
||||||
builder.Services.AddSingleton<UserListState>();
|
builder.Services.AddSingleton<UserListState>();
|
||||||
|
builder.Services.AddSingleton<NotificationState>();
|
||||||
builder.Services.AddSingleton<FilterUserDTO>();
|
builder.Services.AddSingleton<FilterUserDTO>();
|
||||||
|
|
||||||
//Message
|
//Message
|
||||||
builder.Services.AddScoped<IMessenger, WeakReferenceMessenger>();
|
builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>();
|
||||||
builder.Services.AddScoped<NewActivityService>();
|
builder.Services.AddSingleton<NewActivityService>();
|
||||||
builder.Services.AddScoped<BackNavigationService>();
|
builder.Services.AddSingleton<BackNavigationService>();
|
||||||
builder.Services.AddScoped<CopyActivityService>();
|
builder.Services.AddSingleton<CopyActivityService>();
|
||||||
builder.Services.AddScoped<NewContactService>();
|
builder.Services.AddSingleton<NewContactService>();
|
||||||
|
builder.Services.AddSingleton<NewPushNotificationService>();
|
||||||
|
|
||||||
//Notification
|
//Notification
|
||||||
builder.Services.AddNotifications();
|
builder.Services.AddNotifications();
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<script src="_content/salesbook.Shared/js/main.js"></script>
|
<script src="_content/salesbook.Shared/js/main.js"></script>
|
||||||
<script src="_content/salesbook.Shared/js/calendar.js"></script>
|
<script src="_content/salesbook.Shared/js/calendar.js"></script>
|
||||||
<script src="_content/salesbook.Shared/js/alphaScroll.js"></script>
|
<script src="_content/salesbook.Shared/js/alphaScroll.js"></script>
|
||||||
|
<script src="_content/salesbook.Shared/js/notifications.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
@using CommunityToolkit.Mvvm.Messaging
|
@using CommunityToolkit.Mvvm.Messaging
|
||||||
@using salesbook.Shared.Core.Dto
|
@using salesbook.Shared.Core.Dto
|
||||||
@using salesbook.Shared.Core.Dto.Activity
|
@using salesbook.Shared.Core.Dto.Activity
|
||||||
|
@using salesbook.Shared.Core.Dto.Notification
|
||||||
|
@using salesbook.Shared.Core.Dto.PageState
|
||||||
@using salesbook.Shared.Core.Entity
|
@using salesbook.Shared.Core.Entity
|
||||||
@using salesbook.Shared.Core.Messages.Activity.Copy
|
@using salesbook.Shared.Core.Messages.Activity.Copy
|
||||||
@using salesbook.Shared.Core.Messages.Activity.New
|
@using salesbook.Shared.Core.Messages.Activity.New
|
||||||
@using salesbook.Shared.Core.Messages.Contact
|
@using salesbook.Shared.Core.Messages.Contact
|
||||||
|
@using salesbook.Shared.Core.Messages.Notification
|
||||||
@inject IDialogService Dialog
|
@inject IDialogService Dialog
|
||||||
@inject IMessenger Messenger
|
@inject IMessenger Messenger
|
||||||
@inject CopyActivityService CopyActivityService
|
@inject CopyActivityService CopyActivityService
|
||||||
|
@inject NewPushNotificationService NewPushNotificationService
|
||||||
|
@inject NotificationState Notification
|
||||||
|
|
||||||
<div class="container animated-navbar @(IsVisible ? "show-nav" : "hide-nav") @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
<div class="container animated-navbar @(IsVisible ? "show-nav" : "hide-nav") @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
||||||
<nav class="navbar @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
<nav class="navbar @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
||||||
@@ -30,12 +35,14 @@
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
<MudBadge Content="Notification.UnreadNotifications.Count" Visible="!Notification.UnreadNotifications.IsNullOrEmpty()" Color="Color.Error" Overlap="true">
|
||||||
<NavLink class="nav-link" href="Notifications" Match="NavLinkMatch.All">
|
<NavLink class="nav-link" href="Notifications" Match="NavLinkMatch.All">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<i class="ri-notification-4-line"></i>
|
<i class="ri-notification-4-line"></i>
|
||||||
<span>Notifiche</span>
|
<span>Notifiche</span>
|
||||||
</div>
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
</MudBadge>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,6 +70,7 @@
|
|||||||
protected override Task OnInitializedAsync()
|
protected override Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto);
|
CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto);
|
||||||
|
NewPushNotificationService.OnNotificationReceived += NewNotificationReceived;
|
||||||
|
|
||||||
NavigationManager.LocationChanged += (_, args) =>
|
NavigationManager.LocationChanged += (_, args) =>
|
||||||
{
|
{
|
||||||
@@ -104,4 +112,10 @@
|
|||||||
Messenger.Send(new NewContactMessage((CRMCreateContactResponseDTO)result.Data));
|
Messenger.Send(new NewContactMessage((CRMCreateContactResponseDTO)result.Data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NewNotificationReceived(PushNotificationDTO notification)
|
||||||
|
{
|
||||||
|
Notification.UnreadNotifications.Add(notification);
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,45 @@
|
|||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using salesbook.Shared.Components.Layout
|
@using salesbook.Shared.Components.Layout
|
||||||
@using salesbook.Shared.Components.SingleElements
|
@using salesbook.Shared.Components.SingleElements
|
||||||
|
@using salesbook.Shared.Core.Dto.Notification
|
||||||
|
@using salesbook.Shared.Core.Dto.PageState
|
||||||
|
@using salesbook.Shared.Core.Messages.Notification
|
||||||
|
@inject NotificationState Notification
|
||||||
|
@inject NewPushNotificationService NewPushNotificationService
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
<HeaderLayout Title="Notifiche" />
|
<HeaderLayout Title="Notifiche" />
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@if (Notification.UnreadNotifications.IsNullOrEmpty())
|
||||||
|
{
|
||||||
<NoDataAvailable Text="Nessuna notifica meno recente" />
|
<NoDataAvailable Text="Nessuna notifica meno recente" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="list" id="list">
|
||||||
|
@foreach(var notification in Notification.UnreadNotifications)
|
||||||
|
{
|
||||||
|
<NotificationCard Notification="notification" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
protected override Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
NewPushNotificationService.OnNotificationReceived += NewNotificationReceived;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("initNotifications");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NewNotificationReceived(PushNotificationDTO notification)
|
||||||
|
{
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
@using salesbook.Shared.Core.Dto.Notification
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="behind"><button class="trash-btn"><MudIcon Icon="@Icons.Material.Filled.Delete" /></button></div>
|
||||||
|
<div class="notification-card">
|
||||||
|
<div>
|
||||||
|
<div class="title">@Notification.Title</div>
|
||||||
|
<div class="subtitle">@Notification.Message</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public PushNotificationDTO Notification { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
.row {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--mud-default-borderradius);
|
||||||
|
box-shadow: var(--custom-box-shadow);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.behind {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 14px;
|
||||||
|
background: var(--mud-palette-error);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trash-btn {
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-card {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--mud-palette-background);
|
||||||
|
transition: transform .2s ease;
|
||||||
|
touch-action: pan-y;
|
||||||
|
will-change: transform;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: #0b1220;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #22d3ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsing {
|
||||||
|
transition: height .22s ease, margin .22s ease, opacity .22s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace salesbook.Shared.Core.Dto.Notification;
|
||||||
|
|
||||||
|
public class PushNotificationDTO
|
||||||
|
{
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
public DateTime Hours => DateTime.Now;
|
||||||
|
}
|
||||||
8
salesbook.Shared/Core/Dto/PageState/NotificationState.cs
Normal file
8
salesbook.Shared/Core/Dto/PageState/NotificationState.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using salesbook.Shared.Core.Dto.Notification;
|
||||||
|
|
||||||
|
namespace salesbook.Shared.Core.Dto.PageState;
|
||||||
|
|
||||||
|
public class NotificationState
|
||||||
|
{
|
||||||
|
public List<PushNotificationDTO> UnreadNotifications { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using salesbook.Shared.Core.Dto.Notification;
|
||||||
|
|
||||||
|
namespace salesbook.Shared.Core.Messages.Notification;
|
||||||
|
|
||||||
|
public class NewPushNotificationMessage(PushNotificationDTO value) : ValueChangedMessage<PushNotificationDTO>(value);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using salesbook.Shared.Core.Dto.Notification;
|
||||||
|
|
||||||
|
namespace salesbook.Shared.Core.Messages.Notification;
|
||||||
|
|
||||||
|
public class NewPushNotificationService
|
||||||
|
{
|
||||||
|
public event Action<PushNotificationDTO>? OnNotificationReceived;
|
||||||
|
|
||||||
|
public NewPushNotificationService(IMessenger messenger)
|
||||||
|
{
|
||||||
|
messenger.Register<NewPushNotificationMessage>(this, (_, o) => { OnNotificationReceived?.Invoke(o.Value); });
|
||||||
|
}
|
||||||
|
}
|
||||||
85
salesbook.Shared/wwwroot/js/notifications.js
Normal file
85
salesbook.Shared/wwwroot/js/notifications.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const FIRST_THRESHOLD = 80; // rivela pulsante
|
||||||
|
const SECOND_THRESHOLD = 160; // elimina diretta
|
||||||
|
const CLOSE_THRESHOLD = 40; // swipe a destra per richiudere
|
||||||
|
const MAX_SWIPE = 200;
|
||||||
|
|
||||||
|
window.initNotifications = () => {
|
||||||
|
document.querySelectorAll('.row').forEach(initRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
function initRow(row) {
|
||||||
|
const card = row.querySelector('.notification-card');
|
||||||
|
const btn = row.querySelector('.trash-btn');
|
||||||
|
let startX = 0, currentX = 0, dragging = false;
|
||||||
|
let open = false;
|
||||||
|
|
||||||
|
card.addEventListener('pointerdown', (e) => {
|
||||||
|
if (e.pointerType === 'mouse' && e.button !== 0) return;
|
||||||
|
dragging = true;
|
||||||
|
startX = e.clientX;
|
||||||
|
card.setPointerCapture(e.pointerId);
|
||||||
|
card.style.transition = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
card.addEventListener('pointermove', (e) => {
|
||||||
|
if (!dragging) return;
|
||||||
|
const dx = e.clientX - startX;
|
||||||
|
let translate = dx;
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
// swiping left to open/delete
|
||||||
|
translate = Math.max(-MAX_SWIPE, Math.min(0, dx));
|
||||||
|
} else {
|
||||||
|
// if already open, allow swipe right to close
|
||||||
|
translate = Math.min(0, -FIRST_THRESHOLD + dx);
|
||||||
|
}
|
||||||
|
currentX = translate;
|
||||||
|
card.style.transform = `translateX(${translate}px)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
function endDrag() {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false;
|
||||||
|
card.style.transition = 'transform .2s ease';
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
if (currentX < -SECOND_THRESHOLD) {
|
||||||
|
card.style.transform = `translateX(-${MAX_SWIPE}px)`;
|
||||||
|
setTimeout(() => removeRow(row), 200);
|
||||||
|
} else if (currentX < -FIRST_THRESHOLD) {
|
||||||
|
card.style.transform = `translateX(-${FIRST_THRESHOLD}px)`;
|
||||||
|
open = true;
|
||||||
|
} else {
|
||||||
|
card.style.transform = 'translateX(0)';
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// se è già aperto e vado a destra abbastanza → chiudo
|
||||||
|
if (currentX > -FIRST_THRESHOLD + CLOSE_THRESHOLD) {
|
||||||
|
card.style.transform = 'translateX(0)';
|
||||||
|
open = false;
|
||||||
|
} else {
|
||||||
|
card.style.transform = `translateX(-${FIRST_THRESHOLD}px)`;
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
card.addEventListener('pointerup', endDrag);
|
||||||
|
card.addEventListener('pointercancel', endDrag);
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => removeRow(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRow(row) {
|
||||||
|
const h = row.getBoundingClientRect().height;
|
||||||
|
row.style.height = h + 'px';
|
||||||
|
row.classList.add('collapsing');
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
row.style.opacity = '0';
|
||||||
|
row.style.marginTop = '0';
|
||||||
|
row.style.marginBottom = '0';
|
||||||
|
row.style.height = '0';
|
||||||
|
});
|
||||||
|
setTimeout(() => row.remove(), 220);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user