diff --git a/src/MeoAsstGui/Helper/AsstProxy.cs b/src/MeoAsstGui/Helper/AsstProxy.cs index 5d74f19a4c..a216c37201 100644 --- a/src/MeoAsstGui/Helper/AsstProxy.cs +++ b/src/MeoAsstGui/Helper/AsstProxy.cs @@ -11,7 +11,7 @@ using System; using System.Runtime.InteropServices; -using Microsoft.Toolkit.Uwp.Notifications; +using System.Windows; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Stylet; @@ -73,8 +73,11 @@ namespace MeoAsstGui _ptr = AsstCreateEx(System.IO.Directory.GetCurrentDirectory(), _callback, IntPtr.Zero); if (_ptr == IntPtr.Zero) { - _windowManager.ShowMessageBox("出现未知异常", "错误"); - Environment.Exit(0); + Execute.OnUIThread(() => + { + _windowManager.ShowMessageBox("出现未知异常", "错误", icon: MessageBoxImage.Error); + Environment.Exit(0); + }); } var tvm = _container.Get(); tvm.Idle = true; @@ -123,7 +126,7 @@ namespace MeoAsstGui break; case AsstMsg.InitFailed: - _windowManager.ShowMessageBox("初始化错误!请检查是否使用了中文路径", "错误"); + _windowManager.ShowMessageBox("初始化错误!请检查是否使用了中文路径", "错误", icon: MessageBoxImage.Error); Environment.Exit(0); break; @@ -182,7 +185,10 @@ namespace MeoAsstGui case AsstMsg.AllTasksCompleted: mainModel.Idle = true; mainModel.AddLog("任务已全部完成"); - new ToastContentBuilder().AddText("任务已全部完成!").Show(); + using (var toast = new ToastNotification("任务已全部完成!")) + { + toast.Show(); + } mainModel.CheckAndShutdown(); break; } @@ -190,10 +196,11 @@ namespace MeoAsstGui private void procSubTaskMsg(AsstMsg msg, JObject details) { - string taskChain = details["taskchain"].ToString(); - string classType = details["class"].ToString(); + // 下面几行注释暂时没用到,先注释起来... + //string taskChain = details["taskchain"].ToString(); + //string classType = details["class"].ToString(); - var mainModel = _container.Get(); + //var mainModel = _container.Get(); switch (msg) { case AsstMsg.SubTaskError: @@ -293,7 +300,7 @@ namespace MeoAsstGui { case "StageDrops": { - string cur_drops = ""; + string cur_drops = string.Empty; JArray drops = (JArray)subTaskDetails["drops"]; foreach (var item in drops) { @@ -304,7 +311,7 @@ namespace MeoAsstGui cur_drops = cur_drops.EndsWith("\n") ? cur_drops.TrimEnd('\n') : "无"; mainModel.AddLog("当次掉落:\n" + cur_drops); - string all_drops = ""; + string all_drops = string.Empty; JArray statistics = (JArray)subTaskDetails["stats"]; foreach (var item in statistics) { @@ -324,7 +331,7 @@ namespace MeoAsstGui case "RecruitTagsDetected": { JArray tags = (JArray)subTaskDetails["tags"]; - string log_content = ""; + string log_content = string.Empty; foreach (var tag_name in tags) { string tag_str = tag_name.ToString(); @@ -338,7 +345,10 @@ namespace MeoAsstGui case "RecruitSpecialTag": { string special = subTaskDetails["tag"].ToString(); - new ToastContentBuilder().AddText("公招提示:" + special).Show(); + using (var toast = new ToastNotification("公招提示")) + { + toast.AddContentText(special).ShowRecruit(); + } } break; @@ -347,7 +357,10 @@ namespace MeoAsstGui int level = (int)subTaskDetails["level"]; if (level >= 5) { - new ToastContentBuilder().AddText("公招出 " + level + " 星了哦!").Show(); + using (var toast = new ToastNotification($"公招出 {level} 星了哦!")) + { + toast.AddContentText(new string('★', level)).ShowRecruit(row: 2); + } mainModel.AddLog(level + " 星 Tags", "darkorange", "Bold"); } else @@ -360,7 +373,7 @@ namespace MeoAsstGui case "RecruitTagsSelected": { JArray selected = (JArray)subTaskDetails["tags"]; - string selected_log = ""; + string selected_log = string.Empty; foreach (var tag in selected) { selected_log += tag.ToString() + "\n"; @@ -404,13 +417,16 @@ namespace MeoAsstGui case "RecruitSpecialTag": { string special = subTaskDetails["tag"].ToString(); - new ToastContentBuilder().AddText("公招提示:" + special).Show(); + using (var toast = new ToastNotification("公招提示")) + { + toast.AddContentText(special).ShowRecruit(); + } } break; case "RecruitResult": { - string resultContent = ""; + string resultContent = string.Empty; JArray result_array = (JArray)subTaskDetails["result"]; int level = (int)subTaskDetails["level"]; foreach (var combs in result_array) @@ -431,7 +447,10 @@ namespace MeoAsstGui recruitModel.RecruitResult = resultContent; if (level >= 5) { - new ToastContentBuilder().AddText("公招出 " + level + " 星了哦!").Show(); + using (var toast = new ToastNotification($"公招出 {level} 星了哦!")) + { + toast.AddContentText(new string('★', level)).ShowRecruit(row: 2); + } } } break; diff --git a/src/MeoAsstGui/Helper/ScrollViewerBinding.cs b/src/MeoAsstGui/Helper/ScrollViewerBinding.cs index e39a6728a2..60c1451f0e 100644 --- a/src/MeoAsstGui/Helper/ScrollViewerBinding.cs +++ b/src/MeoAsstGui/Helper/ScrollViewerBinding.cs @@ -9,8 +9,10 @@ // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY +using System.Collections.Generic; using System.Windows; using System.Windows.Controls; +using System.Windows.Shapes; namespace MeoAsstGui { @@ -30,54 +32,40 @@ namespace MeoAsstGui /// /// Just a flag that the binding has been applied. /// - private static readonly DependencyProperty VerticalScrollBindingProperty = - DependencyProperty.RegisterAttached("VerticalScrollBinding", typeof(bool?), typeof(ScrollViewerBinding)); + private static readonly DependencyProperty VerticalOffsetBindingProperty = + DependencyProperty.RegisterAttached("VerticalOffsetBinding", typeof(bool?), typeof(ScrollViewerBinding)); public static double GetVerticalOffset(DependencyObject depObj) { - ScrollViewer scrollViewer = depObj as ScrollViewer; - if (scrollViewer == null) + if (!(depObj is ScrollViewer)) return 0; - double maxHeight = scrollViewer.ScrollableHeight; - return (double)depObj.GetValue(VerticalOffsetProperty) / maxHeight; + + return (double)depObj.GetValue(VerticalOffsetProperty); } public static void SetVerticalOffset(DependencyObject depObj, double value) { - if (value <= 1) - { - // by set - ScrollViewer scrollViewer = depObj as ScrollViewer; - if (scrollViewer == null) - return; - double maxHeight = scrollViewer.ScrollableHeight; + if (!(depObj is ScrollViewer)) + return; - depObj.SetValue(VerticalOffsetProperty, value * maxHeight); - } - else - { - // by scroll - depObj.SetValue(VerticalOffsetProperty, value); - } + depObj.SetValue(VerticalOffsetProperty, value); } - private static void OnVerticalOffsetPropertyChanged(DependencyObject d, - DependencyPropertyChangedEventArgs e) + private static void OnVerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ScrollViewer scrollViewer = d as ScrollViewer; - if (scrollViewer == null) + if (!(d is ScrollViewer scrollViewer)) return; BindVerticalOffset(scrollViewer); scrollViewer.ScrollToVerticalOffset((double)e.NewValue); } - public static void BindVerticalOffset(ScrollViewer scrollViewer) + private static void BindVerticalOffset(ScrollViewer scrollViewer) { - if (scrollViewer.GetValue(VerticalScrollBindingProperty) != null) + if (scrollViewer.GetValue(VerticalOffsetBindingProperty) != null) return; - scrollViewer.SetValue(VerticalScrollBindingProperty, true); + scrollViewer.SetValue(VerticalOffsetBindingProperty, true); scrollViewer.ScrollChanged += (s, se) => { if (se.VerticalChange == 0) @@ -88,32 +76,187 @@ namespace MeoAsstGui #endregion VerticalOffset attached property - #region ScrollableHeight attached property + #region ViewportHeight attached property - //public static readonly DependencyProperty ScrollableHeightProperty = - // DependencyProperty.RegisterAttached("ScrollableHeight", typeof(double), - // typeof(ScrollViewerBinding), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnScrollableHeightPropertyChanged)); + public static readonly DependencyProperty ViewportHeightProperty = + DependencyProperty.RegisterAttached("ViewportHeight", typeof(double), + typeof(ScrollViewerBinding), new FrameworkPropertyMetadata(double.NaN, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnViewportHeightPropertyChanged)); - //public static double GetScrollableHeight(DependencyObject depObj) - //{ - // ScrollViewer scrollViewer = depObj as ScrollViewer; - // if (scrollViewer == null) - // return 0; - // return scrollViewer.ScrollableHeight; - //} + private static readonly DependencyProperty ViewportHeightBindingProperty = + DependencyProperty.RegisterAttached("ViewportHeightBinding", typeof(bool?), typeof(ScrollViewerBinding)); - //public static void SetScrollableHeight(DependencyObject depObj, double value) - //{ - // // pass - //} + public static double GetViewportHeight(DependencyObject depObj) + { + if (!(depObj is ScrollViewer scrollViewer)) + return double.NaN; - //private static readonly DependencyProperty ScrollableHeightBindingProperty = - // DependencyProperty.RegisterAttached("ScrollableHeightBinding", typeof(bool?), typeof(ScrollViewerBinding)); + return scrollViewer.ViewportHeight; + } - //private static void OnScrollableHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - //{ - //} + public static void SetViewportHeight(DependencyObject depObj, double value) + { + if (!(depObj is ScrollViewer)) + return; + + depObj.SetValue(ViewportHeightProperty, value); + } + + private static void OnViewportHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (!(d is ScrollViewer scrollViewer)) + return; + + BindViewportHeight(scrollViewer); + } + + private static void BindViewportHeight(ScrollViewer scrollViewer) + { + if (scrollViewer.GetValue(ViewportHeightBindingProperty) != null) + return; + + scrollViewer.SetValue(ViewportHeightBindingProperty, true); + + scrollViewer.Loaded += (s, se) => + { + SetViewportHeight(scrollViewer, scrollViewer.ViewportHeight); + }; + + scrollViewer.ScrollChanged += (s, se) => + { + SetViewportHeight(scrollViewer, se.ViewportHeight); + }; + } + + #endregion ViewportHeight attached property + + #region ExtentHeight attached property + + public static readonly DependencyProperty ExtentHeightProperty = + DependencyProperty.RegisterAttached("ExtentHeight", typeof(double), + typeof(ScrollViewerBinding), new FrameworkPropertyMetadata(double.NaN, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnExtentHeightPropertyChanged)); + + private static readonly DependencyProperty ExtentHeightBindingProperty = + DependencyProperty.RegisterAttached("ExtentHeightBinding", typeof(bool?), typeof(ScrollViewerBinding)); + + public static double GetExtentHeight(DependencyObject depObj) + { + if (!(depObj is ScrollViewer scrollViewer)) + return double.NaN; + + return scrollViewer.ExtentHeight; + } + + public static void SetExtentHeight(DependencyObject depObj, double value) + { + if (!(depObj is ScrollViewer)) + return; + + depObj.SetValue(ExtentHeightProperty, value); + } + + private static void OnExtentHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (!(d is ScrollViewer scrollViewer)) + return; + + BindExtentHeight(scrollViewer); + } + + private static void BindExtentHeight(ScrollViewer scrollViewer) + { + if (scrollViewer.GetValue(ExtentHeightBindingProperty) != null) + return; + + scrollViewer.SetValue(ExtentHeightBindingProperty, true); + + scrollViewer.Loaded += (s, se) => + { + SetExtentHeight(scrollViewer, scrollViewer.ExtentHeight); + }; + } + + #endregion ExtentHeight attached property + + #region RectangleVerticalOffsetList attached property + + /// + /// RectangleIndex attached property + /// + public static readonly DependencyProperty RectangleVerticalOffsetListProperty = + DependencyProperty.RegisterAttached("RectangleVerticalOffsetList", + typeof(List), + typeof(ScrollViewerBinding), + new FrameworkPropertyMetadata(new List(), + FrameworkPropertyMetadataOptions.AffectsRender | + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnRectangleVerticalOffsetListPropertyChanged)); + + /// + /// Just a flag that the binding has been applied. + /// + private static readonly DependencyProperty RectangleVerticalOffsetListBindingProperty = + DependencyProperty.RegisterAttached("RectangleVerticalOffsetListBinding", typeof(bool?), typeof(ScrollViewerBinding)); + + public static List GetRectangleVerticalOffsetList(DependencyObject depObj) + { + return (List)depObj.GetValue(RectangleVerticalOffsetListProperty); + } + + public static void SetRectangleVerticalOffsetList(DependencyObject depObj, List value) + { + if (!(depObj is ScrollViewer)) + return; + + depObj.SetValue(RectangleVerticalOffsetListProperty, value); + } + + private static void OnRectangleVerticalOffsetListPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (!(d is ScrollViewer scrollViewer)) + return; + + BindRectangleVerticalOffsetList(scrollViewer); + } + + private static void BindRectangleVerticalOffsetList(ScrollViewer scrollViewer) + { + + if (scrollViewer.GetValue(RectangleVerticalOffsetListBindingProperty) != null) + return; + + scrollViewer.SetValue(RectangleVerticalOffsetListBindingProperty, true); + + // 当滚动条载入时,遍历 StackPanel 中的所有 Rectangle 子元素对应位置 + scrollViewer.Loaded += (s, se) => + { + if (!scrollViewer.HasContent || !(scrollViewer.Content is StackPanel)) + return; + + var rectangleOffsetList = new List(); + var stackPanel = (StackPanel)scrollViewer.Content; + var point = new Point(0, scrollViewer.VerticalOffset); + + foreach (var child in stackPanel.Children) + { + // 以 Rectangle 为定位元素 + if (!(child is Rectangle)) + continue; + + // 转换计算并保存 Rectangle 在滚动面板中的垂直位置 + var targetPosition = ((FrameworkElement)child).TransformToVisual(scrollViewer).Transform(point); + rectangleOffsetList.Add(targetPosition.Y); + } + + if (rectangleOffsetList.Count > 0) + SetRectangleVerticalOffsetList(scrollViewer, rectangleOffsetList); + }; + } + + #endregion RectangleVerticalOffsetList attached property - #endregion ScrollableHeight attached property } } diff --git a/src/MeoAsstGui/Helper/ToastNotification.cs b/src/MeoAsstGui/Helper/ToastNotification.cs new file mode 100644 index 0000000000..ff493da53f --- /dev/null +++ b/src/MeoAsstGui/Helper/ToastNotification.cs @@ -0,0 +1,373 @@ +// MeoAsstGui - A part of the MeoAssistantArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Media; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.Win32; +using Notification.Wpf; +using Notification.Wpf.Base; +using Notification.Wpf.Constants; +using Notification.Wpf.Controls; + +namespace MeoAsstGui +{ + public class ToastNotification : IDisposable + { + private NotificationManager _notificationManager = new NotificationManager(); + + /// + /// 通知标题 + /// + private string _notificationTitle = string.Empty; + + /// + /// 通知文本内容 + /// + private StringBuilder _contentCollection = new StringBuilder(); + + /// + /// 应用图标资源 + /// + private ImageSource GetAppIcon() + { + try + { + var icon = Icon.ExtractAssociatedIcon(System.Windows.Forms.Application.ExecutablePath); + var imageSource = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + return imageSource; + } + catch (Exception) + { + return new BitmapImage(); + } + } + + /// + /// Toast通知,基于 Notification.Wpf 实现,方便管理通知样式 + /// 建议使用 using 调用,如果不使用 using 调用,建议手动调用 Dispose() 方法释放 + /// + /// 通知标题 + public ToastNotification(string title = null) + { + // 初始化通知标题 + _notificationTitle = title ?? _notificationTitle; + + #region 初始化 Notification.Wpf 默认静态配置 + + // 同时显示最大数量 + NotificationConstants.NotificationsOverlayWindowMaxCount = 5; + + // 默认显示位置 + NotificationConstants.MessagePosition = NotificationPosition.BottomRight; + + // 最小显示宽度 + NotificationConstants.MinWidth = 350d; + + // 最大显示宽度 + NotificationConstants.MaxWidth = 380d; + + #endregion + } + + /// + /// 添加一行内容文本 + /// + /// 文本内容 + /// 返回类本身,可继续调用其它方法 + public ToastNotification AddContentText(string text = null) + { + _contentCollection.AppendLine(text ?? string.Empty); + return this; + } + + #region 通知提示音 + + #region 通知提示音列表枚举 + + public enum NotificationSounds + { + [Description("默认响声")] + Beep, + + [Description("感叹号")] + Exclamation, + + [Description("星号")] + Asterisk, + + [Description("问题")] + Question, + + [Description("关键性停止")] + Hand, + + [Description("通知 (Windows 10 及以上特有,低版本系统直接用也可以)")] + Notification, + + [Description("无声")] + None + } + + #endregion + + /// + /// 播放通知提示音 + /// 如果要播放音频文件,参考微软文档 SoundPlayer 类 + /// + /// 提示音类型 + protected async Task PlayNotificationSoundAsync(NotificationSounds sounds = NotificationSounds.None) + { + await Task.Run(() => + { + + try + { + switch (sounds) + { + case NotificationSounds.Exclamation: SystemSounds.Exclamation.Play(); break; + case NotificationSounds.Asterisk: SystemSounds.Asterisk.Play(); break; + case NotificationSounds.Question: SystemSounds.Question.Play(); break; + case NotificationSounds.Beep: SystemSounds.Beep.Play(); break; + case NotificationSounds.Hand: SystemSounds.Hand.Play(); break; + + case NotificationSounds.Notification: + using (var key = Registry.CurrentUser.OpenSubKey(@"AppEvents\Schemes\Apps\.Default\Notification.Default\.Current")) + { + if (key != null) + { + // 获取 (Default) 项 + var o = key.GetValue(null); + if (o != null) + { + var theSound = new SoundPlayer((string)o); + theSound.Play(); + } + } + else + { + // 如果不支持就播放其它提示音 + PlayNotificationSoundAsync(NotificationSounds.Asterisk); + } + } + break; + + case NotificationSounds.None: + default: break; + } + } + catch (Exception) { } + }).ConfigureAwait(false); + } + + #endregion + + #region 通知按钮设置 + + #region 通知按钮变量 + // 左边按钮 + protected string _buttonLeftText = null; + protected Action _buttonLeftAction = null; + + // 右边按钮 + protected string _buttonRightText = null; + protected Action _buttonRightAction = null; + #endregion + + /// + /// 给通知添加一个在左边的按钮,比如确定按钮,多次设置只会按最后一次设置生效 + /// + /// 按钮标题 + /// 按钮执行方法 + /// 返回类本身,可继续调用其它方法 + public ToastNotification AddButtonLeft(string text, Action action) + { + _buttonLeftText = text; + _buttonLeftAction = action; + return this; + } + + /// + /// 给通知添加一个在右边的按钮,比如取消按钮,多次设置只会按最后一次设置生效 + /// + /// 按钮标题 + /// 按钮执行方法 + /// 返回类本身,可继续调用其它方法 + public ToastNotification AddButtonRight(string text, Action action) + { + _buttonRightText = text; + _buttonRightAction = action; + return this; + } + + #endregion + + #region 通知显示 + + #region 通知基本字体样式和内容模板 + + /// + /// 创建一个基本文本字体样式 + /// + public TextContentSettings BaseTextSettings => new TextContentSettings() + { + FontStyle = FontStyles.Normal, + FontFamily = new System.Windows.Media.FontFamily("Microsoft Yahei"), + FontSize = 14, + FontWeight = FontWeights.Normal, + TextAlignment = TextAlignment.Left, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalTextAlignment = VerticalAlignment.Stretch, + Opacity = 1d + }; + + /// + /// 创建一个基本通知内容模板 + /// + public NotificationContent BaseContent() + { + var content = new NotificationContent() + { + Title = _notificationTitle, + Message = _contentCollection.ToString(), + + Type = NotificationType.None, + Icon = GetAppIcon(), + CloseOnClick = true, + + RowsCount = 1, + TrimType = NotificationTextTrimType.AttachIfMoreRows, + + Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF332A3A"), + + LeftButtonContent = _buttonLeftText ?? NotificationConstants.DefaultLeftButtonContent, + LeftButtonAction = _buttonLeftAction ?? null, + + RightButtonContent = _buttonRightText ?? NotificationConstants.DefaultRightButtonContent, + RightButtonAction = _buttonRightAction ?? null, + }; + + // 默认的标题文本样式 + var titleTextSettings = BaseTextSettings; + titleTextSettings.FontSize = 18d; + content.TitleTextSettings = titleTextSettings; + + // 默认的内容文本样式 + var messageTextSettings = BaseTextSettings; + content.MessageTextSettings = messageTextSettings; + + return content; + } + + #endregion + + #region 显示通知方法 + + /// + /// 显示通知 + /// + /// 通知显示时间 (s) + /// 内容显示行数,如果内容太多建议使用 ShowMore() + /// 播放提示音 + /// 通知内容 + public void Show(double lifeTime = 10d, uint row = 1, + NotificationSounds sound = NotificationSounds.Notification, + NotificationContent notificationContent = null) + { + notificationContent = notificationContent ?? BaseContent(); + + notificationContent.RowsCount = row; + + // 调整显示时间,如果存在按钮的情况下显示时间将强制设为最大时间 + lifeTime = lifeTime < 3d ? 3d : lifeTime; + + var timeSpan = _buttonLeftAction == null && _buttonRightAction == null + ? TimeSpan.FromSeconds(lifeTime) + : TimeSpan.MaxValue; + + // 显示通知 + _notificationManager.Show( + notificationContent, + expirationTime: timeSpan, + ShowXbtn: false); + + // 播放通知提示音 + PlayNotificationSoundAsync(sound); + } + + /// + /// 显示内容更多的通知 + /// + /// 通知显示时间 (s) + /// 内容显示行数,只用于预览一部分通知,多出内容会放在附加按钮的窗口中 + /// 播放提示音,不设置就没有声音 + /// 通知内容 + public void ShowMore(double lifeTime = 12d, uint row = 2, + NotificationSounds sound = NotificationSounds.None, + NotificationContent notificationContent = null) + { + notificationContent = notificationContent ?? BaseContent(); + notificationContent.TrimType = NotificationTextTrimType.Attach; + + Show(lifeTime: lifeTime, + row: row, + sound: sound, + notificationContent: notificationContent); + } + + /// + /// 显示公招特殊标签通知 + /// + /// 内容显示行数,比如第 2 行用来放星星 + public void ShowRecruit(uint row = 1) + { + var content = BaseContent(); + + // 给通知染上资深标签相似的颜色 + content.Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF401804"); + content.Foreground = (SolidColorBrush)new BrushConverter().ConvertFrom("#FFFFC800"); + + Show(row: row, + sound: NotificationSounds.Notification, + notificationContent: content); + } + + #endregion + + #endregion + + /// + /// 通知使用完后释放已使用的数据 + /// + public void Dispose() + { + _contentCollection.Clear(); + + _notificationManager = null; + _notificationTitle = null; + _contentCollection = null; + _buttonLeftText = _buttonRightText = null; + _buttonLeftAction = _buttonRightAction = null; + } + + } +} diff --git a/src/MeoAsstGui/Helper/ViewStatusStorage.cs b/src/MeoAsstGui/Helper/ViewStatusStorage.cs index c3ff5d824b..de24464393 100644 --- a/src/MeoAsstGui/Helper/ViewStatusStorage.cs +++ b/src/MeoAsstGui/Helper/ViewStatusStorage.cs @@ -47,12 +47,9 @@ namespace MeoAsstGui using (StreamReader sr = new StreamReader(_configFilename)) { string jsonStr = sr.ReadToEnd(); - _viewStatus = (JObject)JsonConvert.DeserializeObject(jsonStr); - // 文件存在但为空,会读出来一个null,感觉c#这库有bug - if (_viewStatus == null) - { - _viewStatus = new JObject(); - } + + // 文件存在但为空,会读出来一个null,感觉c#这库有bug,如果是null 就赋值一个空JObject + _viewStatus = (JObject)JsonConvert.DeserializeObject(jsonStr) ?? new JObject(); } } catch (Exception) diff --git a/src/MeoAsstGui/MeoAsstGui.csproj b/src/MeoAsstGui/MeoAsstGui.csproj index 42a6dababa..f966b8a0f8 100644 --- a/src/MeoAsstGui/MeoAsstGui.csproj +++ b/src/MeoAsstGui/MeoAsstGui.csproj @@ -91,6 +91,7 @@ + @@ -114,6 +115,7 @@ + @@ -222,6 +224,7 @@ ResXFileCodeGenerator Resources.Designer.cs + Designer @@ -267,9 +270,6 @@ 5.0.4 - - 7.1.1 - 4.3.0 @@ -279,6 +279,9 @@ 13.0.1 + + 6.1.0.1 + 1.3.6 diff --git a/src/MeoAsstGui/ViewModels/SettingsViewModel.cs b/src/MeoAsstGui/ViewModels/SettingsViewModel.cs index 952cc06cb1..ff16b2b9fc 100644 --- a/src/MeoAsstGui/ViewModels/SettingsViewModel.cs +++ b/src/MeoAsstGui/ViewModels/SettingsViewModel.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using Stylet; using StyletIoC; @@ -121,7 +122,7 @@ namespace MeoAsstGui public List UsesOfDronesList { get; set; } - private int _dormThreshold = System.Convert.ToInt32(ViewStatusStorage.Get("Infrast.DormThreshold", "30")); + private int _dormThreshold = Convert.ToInt32(ViewStatusStorage.Get("Infrast.DormThreshold", "30")); public int DormThreshold { @@ -191,75 +192,109 @@ namespace MeoAsstGui } } - // 消息源,0:无;1:SelectedIndex;2:ScrollOffset - private int _notifySource = 0; + #region 设置页面列表和滚动视图联动绑定 - private void resetNotifySource() + private enum NotifyType { - System.Timers.Timer t = new System.Timers.Timer(20); + None, + SelectedIndex, + ScrollOffset + } + + private NotifyType _notifySource = NotifyType.None; + + private void ResetNotifySource() + { + var t = new System.Timers.Timer(20); t.Elapsed += new System.Timers.ElapsedEventHandler(delegate (object source, System.Timers.ElapsedEventArgs e) { - _notifySource = 0; + _notifySource = NotifyType.None; }); t.AutoReset = false; t.Enabled = true; t.Start(); } + public double ScrollViewportHeight { get; set; } + + public double ScrollExtentHeight { get; set; } + + public List RectangleVerticalOffsetList { get; set; } private int _selectedIndex = 0; - public int SelectedIndex { get { return _selectedIndex; } set { - if (_notifySource == 0) + switch (_notifySource) { - _notifySource = 1; - SetAndNotify(ref _selectedIndex, value); - ScrollOffset = (double)value / ListTitle.Count; - //_notifySource = 0; - resetNotifySource(); - } - else if (_notifySource == 2) - { - SetAndNotify(ref _selectedIndex, value); + case NotifyType.None: + _notifySource = NotifyType.SelectedIndex; + SetAndNotify(ref _selectedIndex, value); + + var isInRange = RectangleVerticalOffsetList != null + && RectangleVerticalOffsetList.Count > 0 + && value < RectangleVerticalOffsetList.Count; + + if (isInRange) + ScrollOffset = RectangleVerticalOffsetList[value]; + + ResetNotifySource(); + break; + + case NotifyType.ScrollOffset: + SetAndNotify(ref _selectedIndex, value); + break; } } } private double _scrollOffset = 0; - public double ScrollOffset { get { return _scrollOffset; } set { - if (_notifySource == 0) + switch (_notifySource) { - _notifySource = 2; - SetAndNotify(ref _scrollOffset, value); - SelectedIndex = (int)(value / ScrollHeight * ListTitle.Count); - //_notifySource = 0; - resetNotifySource(); - } - else if (_notifySource == 1) - { - SetAndNotify(ref _scrollOffset, value); + case NotifyType.None: + _notifySource = NotifyType.ScrollOffset; + SetAndNotify(ref _scrollOffset, value); + + // 设置 ListBox SelectedIndex 为当前 ScrollOffset 索引 + var isInRange = RectangleVerticalOffsetList != null && RectangleVerticalOffsetList.Count > 0; + + if (isInRange) + { + // 滚动条滚动到底部,返回最后一个 Rectangle 索引 + if (ScrollOffset + ScrollViewportHeight >= ScrollExtentHeight) + { + SelectedIndex = RectangleVerticalOffsetList.Count - 1; + ResetNotifySource(); + break; + } + + // 根据出当前 ScrollOffset 选出最后一个在可视范围的 Rectangle 索引 + var rectangleSelect = RectangleVerticalOffsetList.Select((n, i) => ( + rectangleAppeared: value + 10 >= n, + index: i + )); + + var index = rectangleSelect.LastOrDefault(n => n.rectangleAppeared).index; + SelectedIndex = index; + } + + ResetNotifySource(); + break; + + case NotifyType.SelectedIndex: + SetAndNotify(ref _scrollOffset, value); + break; } } } - private double _scrollHeight = 684; // ScrollViewer.ScrollableHeight, 不知道该咋获取,当前的默认值是这个 - - public double ScrollHeight - { - get { return _scrollHeight; } - set - { - SetAndNotify(ref _scrollHeight, value); - } - } + #endregion 设置页面列表和滚动视图联动绑定 /* 信用商店设置 */ @@ -371,7 +406,7 @@ namespace MeoAsstGui } } - private string _proxy = ViewStatusStorage.Get("VersionUpdate.Proxy", ""); + private string _proxy = ViewStatusStorage.Get("VersionUpdate.Proxy", string.Empty); public string Proxy { diff --git a/src/MeoAsstGui/ViewModels/TaskQueueViewModel.cs b/src/MeoAsstGui/ViewModels/TaskQueueViewModel.cs index 06637d71a9..7e40b8c672 100644 --- a/src/MeoAsstGui/ViewModels/TaskQueueViewModel.cs +++ b/src/MeoAsstGui/ViewModels/TaskQueueViewModel.cs @@ -59,14 +59,16 @@ namespace MeoAsstGui } TaskItemViewModels = new ObservableCollection(temp_order_list); - StageList = new List(); - StageList.Add(new CombData { Display = "当前关卡", Value = "" }); - StageList.Add(new CombData { Display = "上次作战", Value = "LastBattle" }); - StageList.Add(new CombData { Display = "剿灭作战", Value = "Annihilation" }); - StageList.Add(new CombData { Display = "龙门币-5", Value = "CE-5" }); - StageList.Add(new CombData { Display = "红票-5", Value = "AP-5" }); - StageList.Add(new CombData { Display = "经验-5", Value = "LS-5" }); - StageList.Add(new CombData { Display = "技能-5", Value = "CA-5" }); + StageList = new List + { + new CombData { Display = "当前关卡", Value = string.Empty }, + new CombData { Display = "上次作战", Value = "LastBattle" }, + new CombData { Display = "剿灭作战", Value = "Annihilation" }, + new CombData { Display = "龙门币-5", Value = "CE-5" }, + new CombData { Display = "红票-5", Value = "AP-5" }, + new CombData { Display = "经验-5", Value = "LS-5" }, + new CombData { Display = "技能-5", Value = "CA-5" } + }; // “风雪过境” 活动关卡 //StageList.Add(new CombData { Display = "BI-7", Value = "BI-7" }); //StageList.Add(new CombData { Display = "BI-8", Value = "BI-8" }); @@ -87,6 +89,8 @@ namespace MeoAsstGui { ClearLog(); + SaveSettingValue(); + AddLog("正在连接模拟器……"); var asstProxy = _container.Get(); @@ -177,6 +181,19 @@ namespace MeoAsstGui Idle = true; } + /// + /// 保存界面设置数值 + /// + private void SaveSettingValue() + { + // 吃理智药个数 + ViewStatusStorage.Set("MainFunction.UseMedicine.Quantity", MedicineNumber); + // 吃石头颗数 + ViewStatusStorage.Set("MainFunction.UseStone.Quantity", StoneNumber); + // 指定刷关次数 + ViewStatusStorage.Set("MainFunction.TimesLimited.Quantity", MaxTimes); + } + private bool appendFight() { int medicine = 0; @@ -272,11 +289,11 @@ namespace MeoAsstGui public void CheckAndShutdown() { - if (Shutdown == true) + if (Shutdown) { System.Diagnostics.Process.Start("shutdown.exe", "-s -t 60"); - var result = _windowManager.ShowMessageBox("已刷完,即将关机,是否取消?", "提示", MessageBoxButton.OK); + var result = _windowManager.ShowMessageBox("已刷完,即将关机,是否取消?", "提示", MessageBoxButton.OK, MessageBoxImage.Question); if (result == MessageBoxResult.OK) { System.Diagnostics.Process.Start("shutdown.exe", "-a"); @@ -334,7 +351,7 @@ namespace MeoAsstGui } } - private string _medicineNumber = "999"; + private string _medicineNumber = ViewStatusStorage.Get("MainFunction.UseMedicine.Quantity", "999"); public string MedicineNumber { @@ -360,7 +377,7 @@ namespace MeoAsstGui } } - private string _stoneNumber = "0"; + private string _stoneNumber = ViewStatusStorage.Get("MainFunction.UseStone.Quantity", "0"); public string StoneNumber { @@ -382,7 +399,7 @@ namespace MeoAsstGui } } - private string _maxTimes = "5"; + private string _maxTimes = ViewStatusStorage.Get("MainFunction.TimesLimited.Quantity", "5"); public string MaxTimes { diff --git a/src/MeoAsstGui/ViewModels/VersionUpdateViewModel.cs b/src/MeoAsstGui/ViewModels/VersionUpdateViewModel.cs index ec2cabb3f8..171b5e7bc0 100644 --- a/src/MeoAsstGui/ViewModels/VersionUpdateViewModel.cs +++ b/src/MeoAsstGui/ViewModels/VersionUpdateViewModel.cs @@ -17,7 +17,6 @@ using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Windows; -using Microsoft.Toolkit.Uwp.Notifications; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Stylet; @@ -116,10 +115,14 @@ namespace MeoAsstGui { return false; } - new ToastContentBuilder() - .AddText("检测到新版本包,正在解压,请稍等……") - .AddText(UpdateTag) - .Show(); + + using (var toast = new ToastNotification("检测到新版本包")) + { + toast.AddContentText("正在解压,请稍等……") + .AddContentText(UpdateTag) + .ShowMore(); + } + string extractDir = Directory.GetCurrentDirectory() + "\\NewVersionExtract"; // 解压 System.IO.Compression.ZipFile.ExtractToDirectory(UpdatePackageName, extractDir); @@ -186,15 +189,23 @@ namespace MeoAsstGui UpdateTag = _lastestJson["name"].ToString(); UpdateInfo = _lastestJson["body"].ToString(); - var openUrlToastButton = new ToastButton(); - openUrlToastButton.SetContent("前往页面查看"); - openUrlToastButton.SetProtocolActivation(new Uri(_viewUrl)); - new ToastContentBuilder() - .AddText("检测到新版本,正在后台下载……") - .AddText(UpdateTag) - .AddText(UpdateInfo) - .AddButton(openUrlToastButton) - .Show(); + var openUrlToastButton = ( + text: "前往页面查看", + action: new Action(() => + { + Process.Start(_viewUrl); + }) + ); + + using (var toast = new ToastNotification("检测到新版本")) + { + toast.AddContentText("正在后台下载……") + .AddContentText(UpdateTag) + .AddContentText(UpdateInfo) + .AddButtonLeft(openUrlToastButton.text, openUrlToastButton.action) + .ShowMore(row: 3); + } + // 下载压缩包 const int downloadRetryMaxTimes = 2; string downloadTempFilename = UpdatePackageName + ".tmp"; @@ -208,23 +219,30 @@ namespace MeoAsstGui break; } } + if (!downloaded) { - new ToastContentBuilder() - .AddText("新版本下载失败") - .AddText("请尝试手动下载后,将压缩包放到目录下_(:з」∠)_") - .AddButton(openUrlToastButton) - .Show(); + using (var toast = new ToastNotification("新版本下载失败")) + { + toast.AddContentText("请尝试手动下载后,将压缩包放到目录下_(:з」∠)_") + .AddButtonLeft(openUrlToastButton.text, openUrlToastButton.action) + .Show(); + } + return false; } + File.Copy(downloadTempFilename, UpdatePackageName, true); File.Delete(downloadTempFilename); + // 把相关信息存下来,更新完之后启动的时候显示 - new ToastContentBuilder() - .AddText("新版本下载完成") - .AddText("软件将在下次启动时自动更新!") - .AddText("✿✿ヽ(°▽°)ノ✿") - .Show(); + using (var toast = new ToastNotification("新版本下载完成")) + { + toast.AddContentText("软件将在下次启动时自动更新!") + .AddContentText("✿✿ヽ(°▽°)ノ✿") + .Show(lifeTime: 10d, row: 2); + } + return true; } @@ -293,18 +311,19 @@ namespace MeoAsstGui private string RequestApi(string url) { - HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); - httpWebRequest.Method = "GET"; - httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"; - httpWebRequest.Accept = "application/vnd.github.v3+json"; - var settings = _container.Get(); - if (settings.Proxy.Length > 0) - { - httpWebRequest.Proxy = new WebProxy(settings.Proxy); - } - //httpWebRequest.Timeout = 20000; try { + HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); + httpWebRequest.Method = "GET"; + httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"; + httpWebRequest.Accept = "application/vnd.github.v3+json"; + var settings = _container.Get(); + if (settings.Proxy.Length > 0) + { + httpWebRequest.Proxy = new WebProxy(settings.Proxy); + } + //httpWebRequest.Timeout = 20000; + HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.UTF8); string responseContent = streamReader.ReadToEnd(); @@ -423,18 +442,20 @@ namespace MeoAsstGui ViewStatusStorage.Set(filename, cloud_sha); File.Delete(tempname); } - if (updated) - { - new ToastContentBuilder() - .AddText("资源已更新,重启软件生效!") - .AddText(message) - .Show(); - return true; - } - else + + if (!updated) { return false; } + + using (var toast = new ToastNotification("资源已更新")) + { + toast.AddContentText("重启软件生效!") + .AddContentText(message) + .ShowMore(); + } + + return true; } private static void CopyFilesRecursively(string sourcePath, string targetPath) @@ -456,8 +477,8 @@ namespace MeoAsstGui { RequestClose(); IsFirstBootAfterUpdate = false; - UpdateTag = ""; - UpdateInfo = ""; + UpdateTag = string.Empty; + UpdateInfo = string.Empty; } } } diff --git a/src/MeoAsstGui/Views/SettingsView.xaml b/src/MeoAsstGui/Views/SettingsView.xaml index 29b43ddfac..71c498ded5 100644 --- a/src/MeoAsstGui/Views/SettingsView.xaml +++ b/src/MeoAsstGui/Views/SettingsView.xaml @@ -16,9 +16,11 @@ - - +