diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/MainView.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/MainView.axaml
index 818b22273..628e705d6 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/MainView.axaml
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/MainView.axaml
@@ -162,7 +162,8 @@
FallbackSource="avares://System.Application.SteamTools.Client.Avalonia/Application/UI/Assets/AppResources/avatar.jpg">
-
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/UserCenter/UserItem.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/UserCenter/UserItem.axaml
index 14ff26b7a..dbb514ab8 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/UserCenter/UserItem.axaml
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/UserCenter/UserItem.axaml
@@ -126,7 +126,9 @@
CommandParameter="Notice">
-
-
-->
-
@@ -94,20 +95,34 @@
Margin="10"
IsVisible="{Binding Path=!IsEmpty,Mode=OneWay}">
-
+
-
+
+
+
+
+
@@ -140,7 +155,7 @@
DecodeWidth="{ReflectionBinding $parent.Width}"
Margin="10 0"
Source="{Binding NoticeItem.PictureStream^}"
- IsVisible="{Binding NoticeItem.PictureStream^,Converter={StaticResource IsNullConverter},ConverterParameter=invert}}"
+ IsVisible="{Binding NoticeItem.PictureStream,Converter={StaticResource IsNullConverter},ConverterParameter=invert}}"
Stretch="Uniform" />
("TitleBarHost") is IControl t)
{
@@ -109,8 +109,7 @@ namespace Avalonia.Controls
//ExtendClientAreaToDecorationsHint = true;
//ExtendClientAreaTitleBarHeightHint = -1;
- ExtendClientAreaChromeHints =
- ExtendClientAreaChromeHints.Default;
+ ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
}
else
{
@@ -131,8 +130,7 @@ namespace Avalonia.Controls
}
else
{
- ExtendClientAreaChromeHints =
- ExtendClientAreaChromeHints.SystemChrome;
+ ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
}
#pragma warning restore CA1416 // 验证平台兼容性
diff --git a/src/ST.Client/Entities/Notification.cs b/src/ST.Client/Entities/Notification.cs
new file mode 100644
index 000000000..dc369493e
--- /dev/null
+++ b/src/ST.Client/Entities/Notification.cs
@@ -0,0 +1,30 @@
+using SQLite;
+using System.Application.Columns;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using NotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute;
+using SQLiteNotNull = SQLite.NotNullAttribute;
+using SQLiteTable = SQLite.TableAttribute;
+
+namespace System.Application.Entities
+{
+ ///
+ /// 通知记录
+ ///
+ [SQLiteTable("A765AD32")]
+ [DebuggerDisplay("{DebuggerDisplay(),nq}")]
+ public class Notification : IEntity
+ {
+ string DebuggerDisplay => Id.ToString();
+
+ [Column("2ABD43F2")]
+ [PrimaryKey]
+ public Guid Id { get; set; }
+
+ [Column("4557AC21")]
+ public DateTimeOffset ExpirationTime { get; set; } = DateTimeOffset.Now;
+
+ [Column("8E7393C5")]
+ public bool HasRead { get; set; }
+ }
+}
diff --git a/src/ST.Client/KeyConstants.cs b/src/ST.Client/KeyConstants.cs
index 886a2326f..61451d7e0 100644
--- a/src/ST.Client/KeyConstants.cs
+++ b/src/ST.Client/KeyConstants.cs
@@ -6,10 +6,5 @@ namespace System.Application
/// 当前登录用户
///
public const string KEY_CURRENT_LOGIN_USER = "KEY_CURRENT_LOGIN_USER";
-
- ///
- /// 上次启动时收到的通知纪录 ID
- ///
- public const string ON_LAST_STARTUP_NOTIFICATION_RECORD_ID = "ON_LAST_STARTUP_NOTIFICATION_RECORD_ID";
}
}
\ No newline at end of file
diff --git a/src/ST.Client/Repositories/INotificationRepository.cs b/src/ST.Client/Repositories/INotificationRepository.cs
new file mode 100644
index 000000000..51171ca06
--- /dev/null
+++ b/src/ST.Client/Repositories/INotificationRepository.cs
@@ -0,0 +1,15 @@
+using System.Application.Entities;
+using System.Application.Models;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Application.Repositories
+{
+ public interface INotificationRepository : IRepository
+ {
+
+ Task> GetAllAsync(Expression>? expression = null);
+ }
+}
diff --git a/src/ST.Client/Repositories/Implementation/NotificationRepository.cs b/src/ST.Client/Repositories/Implementation/NotificationRepository.cs
new file mode 100644
index 000000000..c87095e17
--- /dev/null
+++ b/src/ST.Client/Repositories/Implementation/NotificationRepository.cs
@@ -0,0 +1,25 @@
+using AutoMapper;
+using System.Application.Entities;
+using System.Application.Models;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Application.Repositories.Implementation
+{
+ internal sealed class NotificationRepository : Repository, INotificationRepository
+ {
+ public async Task> GetAllAsync(Expression>? expression = null)
+ {
+ var dbConnection = await GetDbConnection().ConfigureAwait(false);
+ return await AttemptAndRetry(async () =>
+ {
+ var r = dbConnection.Table();
+ if (expression != null)
+ r = r.Where(expression);
+ return await r.ToArrayAsync();
+ }).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/ST.Client/ServiceCollectionExtensions.cs b/src/ST.Client/ServiceCollectionExtensions.cs
index 30a152b03..0a8a317f2 100644
--- a/src/ST.Client/ServiceCollectionExtensions.cs
+++ b/src/ST.Client/ServiceCollectionExtensions.cs
@@ -20,6 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
return services;
}
diff --git a/src/ST.Client/Services/INotificationService.cs b/src/ST.Client/Services/INotificationService.cs
index 28bec654c..9d88e709f 100644
--- a/src/ST.Client/Services/INotificationService.cs
+++ b/src/ST.Client/Services/INotificationService.cs
@@ -101,14 +101,6 @@ namespace System.Application.Services
}
- ///
- /// 获取最后一次收到的通知启用时间 只提示大于此时间的通知
- ///
- ///
- static Task GetLastNotificationTime() => ISecureStorage.Instance.GetAsync(ON_LAST_STARTUP_NOTIFICATION_RECORD_ID);
-
- static Task SetLastNotificationTime(DateTimeOffset time) => ISecureStorage.Instance.SetAsync(ON_LAST_STARTUP_NOTIFICATION_RECORD_ID, time);
-
///
/// NotifyIcon / TrayIcon 右下角托盘菜单助手类
///
diff --git a/src/ST.Client/Services/Mvvm/NotificationService.cs b/src/ST.Client/Services/Mvvm/NotificationService.cs
index 97cf850ba..56ebb7a2a 100644
--- a/src/ST.Client/Services/Mvvm/NotificationService.cs
+++ b/src/ST.Client/Services/Mvvm/NotificationService.cs
@@ -4,6 +4,7 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.Application.Models;
using System.Application.Properties;
+using System.Application.Repositories;
using System.Application.Settings;
using System.Application.UI.Resx;
using System.Collections.Generic;
@@ -30,6 +31,7 @@ namespace System.Application.Services
public static NotificationService Current => mCurrent ?? new();
readonly IHttpService httpService = DI.Get();
+ readonly INotificationRepository notificationRepository = DI.Get();
public SourceCache NoticesSource { get; }
@@ -39,6 +41,8 @@ namespace System.Application.Services
[Reactive]
public ObservableCollection NoticeTypes { get; set; }
+ public NoticeTypeDTO DefaultType { get; } = new NoticeTypeDTO() { Name = AppResources.AllTyoe };
+
public NotificationService()
{
mCurrent = this;
@@ -48,38 +52,39 @@ namespace System.Application.Services
public async Task GetNews(int trycount = 0)
{
- var lastTime = await INotificationService.GetLastNotificationTime();
+ var data = await notificationRepository.GetAllAsync(w => w.ExpirationTime > DateTimeOffset.Now);
+ UnreadNotificationsCount = data.Count(x => !x.HasRead);
var client = ICloudServiceClient.Instance.Notice;
var result = await client.NewMsg(null, null);
if (result.IsSuccess && result.Content != null)
{
- var noticeList = result.Content.Where(x => lastTime.HasValue ? x.EnableTime > lastTime : true);
- if (noticeList.Count() > 1)
+ var noticeList = result.Content.Where(x => !data.Any(d => d.Id == x.Id));
+ if (noticeList.Any_Nullable())
{
- await INotificationService.SetLastNotificationTime(noticeList.Max(x => x.EnableTime));
- INotificationService.Instance.Notify(new NotificationBuilder()
+ if (noticeList.Count() > 1)
{
- Title = AppResources.NotificationChannelType_Description_Announcement,
- Content = AppResources.Notice_Tray.Format(result.Content.Length),
- AutoCancel = NotificationBuilder.DefaultAutoCancel,
- Type = NotificationType.Announcement,
- Click = new NotificationBuilder.ClickAction(() =>
+ INotificationService.Instance.Notify(new NotificationBuilder()
{
- IWindowManager.Instance.Show(CustomWindow.Notice);
- })
- });
- }
- else
- {
- var notice = noticeList.FirstOrDefault();
- if (notice != null && notice.EnableTime > lastTime)
+ Title = AppResources.Notice_Tray_Title,
+ Content = AppResources.Notice_Tray_Content.Format(result.Content.Length),
+ AutoCancel = NotificationBuilder.DefaultAutoCancel,
+ Type = NotificationType.Announcement,
+ Click = new NotificationBuilder.ClickAction(() =>
+ {
+ IWindowManager.Instance.Show(CustomWindow.Notice);
+ })
+ });
+ }
+ else
{
- await INotificationService.SetLastNotificationTime(notice.EnableTime);
+ var notice = noticeList.First();
INotificationService.Instance.Notify(notice);
}
+ await InsertNotificationRecord(noticeList);
}
+ DeleteExpiredRecordAsync();
}
}
@@ -90,6 +95,7 @@ namespace System.Application.Services
if (result.IsSuccess)
{
NoticeTypes.Clear();
+ NoticeTypes.Add(DefaultType);
NoticeTypes.AddRange(result.Content!.OrderBy(x => x.Order));
}
}
@@ -97,7 +103,9 @@ namespace System.Application.Services
public async Task LoadNotification(NoticeTypeDTO? selectGroup)
{
var client = ICloudServiceClient.Instance.Notice;
- var result = await client.Table(selectGroup?.Id, 1, 30);
+ var typeid = selectGroup == DefaultType ? null : selectGroup?.Id;
+ var isAll = typeid is null;
+ var result = await client.Table(typeid, 1, 30);
if (result.IsSuccess && result.Content != null)
{
//foreach (var item in result.Content!.DataSource)
@@ -107,7 +115,59 @@ namespace System.Application.Services
//}
NoticesSource.Clear();
NoticesSource.AddOrUpdate(result.Content.DataSource);
+ if (isAll)
+ {
+ await LoadHasReadRecord();
+ }
}
}
+
+ public async Task LoadHasReadRecord()
+ {
+ var data = await notificationRepository.GetAllAsync(w => w.ExpirationTime > DateTimeOffset.Now);
+ if (data.Any_Nullable())
+ {
+ //var oldData = data.Where(w => w.ExpirationTime > DateTimeOffset.Now);
+ //if (oldData.Any())
+ // await notificationRepository.DeleteRangeAsync(oldData);
+ UnreadNotificationsCount = data.Count(x => !x.HasRead);
+
+ foreach (var d in data)
+ {
+ var notice = NoticesSource.Lookup(d.Id);
+ if (notice.HasValue)
+ notice.Value.HasRead = d.HasRead;
+ }
+ }
+ }
+
+ public async Task InsertNotificationRecord(IEnumerable notices)
+ {
+ var data = await notificationRepository.GetAllAsync(w => w.ExpirationTime > DateTimeOffset.Now);
+ var newData = notices
+ .Where(w => !data.Any(d => d.Id == w.Id))
+ .Select(s => new Entities.Notification()
+ {
+ Id = s.Id,
+ HasRead = false,
+ ExpirationTime = s.OverdueTime,
+ });
+ var num = await notificationRepository.InsertRangeAsync(newData);
+ if (num > 0)
+ {
+ UnreadNotificationsCount += num;
+ }
+ }
+
+ public async Task MarkNotificationHasRead(params Entities.Notification[] notice)
+ {
+ await notificationRepository.UpdateRangeAsync(notice);
+ }
+
+ public async void DeleteExpiredRecordAsync()
+ {
+ var data = await notificationRepository.GetAllAsync(w => w.ExpirationTime < DateTimeOffset.Now);
+ await notificationRepository.DeleteRangeAsync(data);
+ }
}
}
diff --git a/src/ST.Client/UI/Resx/AppResources.Designer.cs b/src/ST.Client/UI/Resx/AppResources.Designer.cs
index 8772086bf..b582aa9af 100644
--- a/src/ST.Client/UI/Resx/AppResources.Designer.cs
+++ b/src/ST.Client/UI/Resx/AppResources.Designer.cs
@@ -515,6 +515,15 @@ namespace System.Application.UI.Resx {
}
}
+ ///
+ /// 查找类似 全部类型 的本地化字符串。
+ ///
+ public static string AllTyoe {
+ get {
+ return ResourceManager.GetString("AllTyoe", resourceCulture);
+ }
+ }
+
///
/// 查找类似 和 的本地化字符串。
///
@@ -4069,11 +4078,38 @@ namespace System.Application.UI.Resx {
}
///
- /// 查找类似 当前有 {0} 条{1}通知,详情请在程序内通知公告处查看。 的本地化字符串。
+ /// 查找类似 全部标记为已读 的本地化字符串。
///
- public static string Notice_Tray {
+ public static string Notice_AllMarkHasRead {
get {
- return ResourceManager.GetString("Notice_Tray", resourceCulture);
+ return ResourceManager.GetString("Notice_AllMarkHasRead", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 收到{0}条新消息,详情请在程序内我的消息处查看。 的本地化字符串。
+ ///
+ public static string Notice_Tray_Content {
+ get {
+ return ResourceManager.GetString("Notice_Tray_Content", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 消息提示 的本地化字符串。
+ ///
+ public static string Notice_Tray_Title {
+ get {
+ return ResourceManager.GetString("Notice_Tray_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 消息类型 的本地化字符串。
+ ///
+ public static string Notice_Type {
+ get {
+ return ResourceManager.GetString("Notice_Type", resourceCulture);
}
}
diff --git a/src/ST.Client/UI/Resx/AppResources.resx b/src/ST.Client/UI/Resx/AppResources.resx
index 1b3b1239e..53d1d7179 100644
--- a/src/ST.Client/UI/Resx/AppResources.resx
+++ b/src/ST.Client/UI/Resx/AppResources.resx
@@ -2272,8 +2272,11 @@ AAAAA-BBBBB-CCCC3
请正确选择一张要应用的图片
-
- 当前有 {0} 条{1}通知,详情请在程序内通知公告处查看。
+
+ 消息提示
+
+
+ 收到{0}条新消息,详情请在程序内我的消息处查看。
证书状态
@@ -2365,4 +2368,13 @@ AAAAA-BBBBB-CCCC3
查看详情
+
+ 全部类型
+
+
+ 全部标记为已读
+
+
+ 消息类型
+
\ No newline at end of file
diff --git a/src/ST.Client/UI/ViewModels/Windows/UserCenter/NoticeWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/UserCenter/NoticeWindowViewModel.cs
index f4058dfb3..c72b476d9 100644
--- a/src/ST.Client/UI/ViewModels/Windows/UserCenter/NoticeWindowViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/Windows/UserCenter/NoticeWindowViewModel.cs
@@ -39,9 +39,20 @@ namespace System.Application.UI.ViewModels
{
Title = GetTitleByDisplayName(DisplayName);
//OpenNotice = ReactiveCommand.CreateFromTask(x => Browser2.OpenAsync(x));
- MarkAllRead = ReactiveCommand.Create(() =>
+ MarkAllRead = ReactiveCommand.CreateFromTask(async () =>
{
+ var data = NotificationService.Current.NoticesSource.Items.Select(s => new Entities.Notification
+ {
+ Id = s.Id,
+ ExpirationTime = s.OverdueTime,
+ HasRead = true
+ }).ToArray();
+ await NotificationService.Current.MarkNotificationHasRead(data);
+ foreach (var item in NotificationService.Current.NoticesSource.Items)
+ {
+ item.HasRead = true;
+ }
});
NotificationService.Current.NoticesSource
@@ -62,6 +73,21 @@ namespace System.Application.UI.ViewModels
await NotificationService.Current.LoadNotification(x);
IsLoading = false;
});
+
+ this.WhenValueChanged(x => x.NoticeItem)
+ .Subscribe(x =>
+ {
+ if (x != null && !x.HasRead)
+ {
+ _ = NotificationService.Current.MarkNotificationHasRead(new Entities.Notification
+ {
+ Id = x.Id,
+ ExpirationTime = x.OverdueTime,
+ HasRead = true
+ });
+ x.HasRead = true;
+ }
+ });
}
public override async void Activation()
@@ -69,6 +95,7 @@ namespace System.Application.UI.ViewModels
//if (IsFirstActivation)
IsLoading = true;
await NotificationService.Current.LoadNoticeTypes();
+ SelectType = NotificationService.Current.NoticeTypes.FirstOrDefault();
await NotificationService.Current.LoadNotification(SelectType);
IsLoading = false;
base.Activation();
diff --git a/src/ST.Services.CloudService.Models/Models/Notice/NoticeDTO.cs b/src/ST.Services.CloudService.Models/Models/Notice/NoticeDTO.cs
index c630dc5ff..82ac2a780 100644
--- a/src/ST.Services.CloudService.Models/Models/Notice/NoticeDTO.cs
+++ b/src/ST.Services.CloudService.Models/Models/Notice/NoticeDTO.cs
@@ -102,16 +102,6 @@ namespace System.Application.Models
[S_JsonProperty("9")]
public DateTimeOffset OverdueTime { get; set; }
-#if MVVM_VM
- ///
- /// 是否过期
- ///
- [MPIgnore]
- [N_JsonIgnore]
- [S_JsonIgnore]
- public bool Overdue => DateTimeOffset.Now > OverdueTime;
-#endif
-
///
/// 通知渠道
///
@@ -129,5 +119,24 @@ namespace System.Application.Models
[N_JsonProperty("12")]
[S_JsonProperty("12")]
public string? Context { get; set; } = string.Empty;
+
+#if MVVM_VM
+ ///
+ /// 是否过期
+ ///
+ [MPIgnore]
+ [N_JsonIgnore]
+ [S_JsonIgnore]
+ public bool Overdue => DateTimeOffset.Now > OverdueTime;
+
+ ///
+ /// 是否已读
+ ///
+ [MPIgnore]
+ [N_JsonIgnore]
+ [S_JsonIgnore]
+ [Reactive]
+ public bool HasRead { get; set; }
+#endif
}
}