Add 'New Update' notification flow
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions

Introduce a small update-notification feature: add AppEvents.HasUpdateNotified event and have TaskManager publish it when update messages exist. Add localized resource key (menuNewUpdate) and expose it in the ResUI designer; update resource files for several languages. In the UI, add a New Update button in MainWindow.xaml, wire its click to the existing check-update handler, bind its visibility to a new BlNewUpdate property on MainWindowViewModel, and subscribe the viewmodel to the new event. Also reset the notification flag after showing the Check Update dialog.
This commit is contained in:
2dust 2026-05-21 11:25:49 +08:00
parent c7519f8ea7
commit 14cc37d07a
15 changed files with 66 additions and 0 deletions

View file

@ -7,6 +7,7 @@ public static class AppEvents
public static readonly EventChannel<Unit> AddServerViaScanRequested = new(); public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new(); public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new(); public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
public static readonly EventChannel<bool> HasUpdateNotified = new();
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new(); public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new(); public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();

View file

@ -142,5 +142,10 @@ public class TaskManager
await _updateFunc?.Invoke(false, msg); await _updateFunc?.Invoke(false, msg);
} }
NoticeManager.Instance.Enqueue(string.Join("\n", msgs)); NoticeManager.Instance.Enqueue(string.Join("\n", msgs));
if (msgs.Count > 0)
{
AppEvents.HasUpdateNotified.Publish(true);
}
} }
} }

View file

@ -1320,6 +1320,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 New Update 的本地化字符串。
/// </summary>
public static string menuNewUpdate {
get {
return ResourceManager.GetString("menuNewUpdate", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Open the storage location 的本地化字符串。 /// 查找类似 Open the storage location 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1743,4 +1743,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value> <value>IP Info</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value> <value>IP Info</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1743,4 +1743,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value> <value>IP Info</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1743,4 +1743,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value> <value>IP Info</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1743,4 +1743,7 @@
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value> <value>IP Info</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,7 @@
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP 信息</value> <value>IP 信息</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,7 @@
<data name="LvTestIpInfo" xml:space="preserve"> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP 資訊</value> <value>IP 資訊</value>
</data> </data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root> </root>

View file

@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject
[Reactive] public bool BlIsWindows { get; set; } [Reactive] public bool BlIsWindows { get; set; }
[Reactive] public bool BlNewUpdate { get; set; }
#endregion Menu #endregion Menu
#region Init #region Init
@ -251,6 +253,11 @@ public class MainWindowViewModel : MyReactiveObject
.ObserveOn(RxSchedulers.MainThreadScheduler) .ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async bl => BlNewUpdate = bl);
#endregion AppEvents #endregion AppEvents
_ = Init(); _ = Init();

View file

@ -102,6 +102,13 @@
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" /> <MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />
</Menu> </Menu>
<Button
x:Name="btnNewUpdate"
Margin="{StaticResource MarginLr8}"
HorizontalAlignment="Left"
Content="{x:Static resx:ResUI.menuNewUpdate}"
IsVisible="False" />
</DockPanel> </DockPanel>
<view:StatusBarView DockPanel.Dock="Bottom" /> <view:StatusBarView DockPanel.Dock="Bottom" />

View file

@ -25,6 +25,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click; menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
menuPromotion.Click += MenuPromotion_Click; menuPromotion.Click += MenuPromotion_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click; menuCheckUpdate.Click += MenuCheckUpdate_Click;
btnNewUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click; menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
menuClose.Click += MenuClose_Click; menuClose.Click += MenuClose_Click;
@ -102,6 +103,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.IsVisible).DisposeWith(disposables);
switch (_config.UiItem.MainGirdOrientation) switch (_config.UiItem.MainGirdOrientation)
{ {
@ -367,6 +369,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
_checkUpdateView ??= new CheckUpdateView(); _checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView); DialogHost.Show(_checkUpdateView);
AppEvents.HasUpdateNotified.Publish(false);
} }
private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e) private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e)

View file

@ -313,6 +313,13 @@
Style="{StaticResource MaterialDesignToolForegroundPopupBox}"> Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
<ContentControl x:Name="pbTheme" /> <ContentControl x:Name="pbTheme" />
</materialDesign:PopupBox> </materialDesign:PopupBox>
<Button
x:Name="btnNewUpdate"
Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.menuNewUpdate}"
Style="{StaticResource DefButton}"
Visibility="Hidden" />
</ToolBar> </ToolBar>
</ToolBarTray> </ToolBarTray>

View file

@ -25,6 +25,7 @@ public partial class MainWindow
menuPromotion.Click += MenuPromotion_Click; menuPromotion.Click += MenuPromotion_Click;
menuClose.Click += MenuClose_Click; menuClose.Click += MenuClose_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click; menuCheckUpdate.Click += MenuCheckUpdate_Click;
btnNewUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click; menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
ViewModel = new MainWindowViewModel(UpdateViewHandler); ViewModel = new MainWindowViewModel(UpdateViewHandler);
@ -102,6 +103,8 @@ public partial class MainWindow
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.Visibility).DisposeWith(disposables);
switch (_config.UiItem.MainGirdOrientation) switch (_config.UiItem.MainGirdOrientation)
{ {
case EGirdOrientation.Horizontal: case EGirdOrientation.Horizontal:
@ -363,6 +366,8 @@ public partial class MainWindow
{ {
_checkUpdateView ??= new CheckUpdateView(); _checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView, "RootDialog"); DialogHost.Show(_checkUpdateView, "RootDialog");
AppEvents.HasUpdateNotified.Publish(false);
} }
private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e) private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e)