添加首页搜索框服务、剪贴板服务,修复一些问题

v2.0.x
leiurayer 2 years ago
parent 9392c10779
commit f01c44a473

@ -50,13 +50,16 @@ public partial class SettingsManager
/// <returns></returns> /// <returns></returns>
private AppSettings GetSettings() private AppSettings GetSettings()
{ {
if (appSettings != null) { return appSettings; }
try try
{ {
var fileStream = new FileStream(settingsName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); string jsonWordTemplate = string.Empty;
var streamReader = new StreamReader(fileStream, System.Text.Encoding.UTF8); using (var stream = new FileStream(settingsName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
string jsonWordTemplate = streamReader.ReadToEnd(); {
streamReader.Close(); using var reader = new StreamReader(stream, System.Text.Encoding.UTF8);
fileStream.Close(); jsonWordTemplate = reader.ReadToEnd();
}
#if DEBUG #if DEBUG
#else #else

@ -9,8 +9,12 @@ public class BaseServices
public INotificationEvent NotificationEvent { get; } public INotificationEvent NotificationEvent { get; }
public IClipboardService ClipboardService { get; }
public IDictionaryResource DictionaryResource { get; } public IDictionaryResource DictionaryResource { get; }
public IMainSearchService MainSearchService { get; }
public INavigationService NavigationService { get; } public INavigationService NavigationService { get; }
public IStoragePicker StoragePicker { get; } public IStoragePicker StoragePicker { get; }
@ -19,14 +23,18 @@ public class BaseServices
public BaseServices(IBroadcastEvent broadcastEvent, public BaseServices(IBroadcastEvent broadcastEvent,
INotificationEvent notificationEvent, INotificationEvent notificationEvent,
IClipboardService clipboardService,
IDictionaryResource dictionaryResource, IDictionaryResource dictionaryResource,
IMainSearchService mainSearchService,
INavigationService navigationService, INavigationService navigationService,
IStoragePicker storagePicker, IStoragePicker storagePicker,
IStorageService storageService) IStorageService storageService)
{ {
BroadcastEvent = broadcastEvent; BroadcastEvent = broadcastEvent;
NotificationEvent = notificationEvent; NotificationEvent = notificationEvent;
ClipboardService = clipboardService;
DictionaryResource = dictionaryResource; DictionaryResource = dictionaryResource;
MainSearchService = mainSearchService;
NavigationService = navigationService; NavigationService = navigationService;
StoragePicker = storagePicker; StoragePicker = storagePicker;
StorageService = storageService; StorageService = storageService;

@ -5,4 +5,4 @@ public interface INavigationAware
void OnNavigatedTo(Dictionary<string, object>? parameter); void OnNavigatedTo(Dictionary<string, object>? parameter);
void OnNavigatedFrom(Dictionary<string, object>? parameter); void OnNavigatedFrom(Dictionary<string, object>? parameter);
} }

@ -11,7 +11,9 @@ public abstract class ViewModelBase : ObservableObject, INavigationAware
protected IBroadcastEvent BroadcastEvent => _baseServices.BroadcastEvent; protected IBroadcastEvent BroadcastEvent => _baseServices.BroadcastEvent;
protected INotificationEvent NotificationEvent => _baseServices.NotificationEvent; protected INotificationEvent NotificationEvent => _baseServices.NotificationEvent;
protected IClipboardService ClipboardService => _baseServices.ClipboardService;
protected IDictionaryResource DictionaryResource => _baseServices.DictionaryResource; protected IDictionaryResource DictionaryResource => _baseServices.DictionaryResource;
protected IMainSearchService MainSearchService => _baseServices.MainSearchService;
protected INavigationService NavigationService => _baseServices.NavigationService; protected INavigationService NavigationService => _baseServices.NavigationService;
protected IStoragePicker StoragePicker => _baseServices.StoragePicker; protected IStoragePicker StoragePicker => _baseServices.StoragePicker;
protected IStorageService StorageService => _baseServices.StorageService; protected IStorageService StorageService => _baseServices.StorageService;

@ -0,0 +1,6 @@
namespace Downkyi.UI.Services;
public interface IClipboardService
{
Task<string> GetTextAsync();
}

@ -0,0 +1,28 @@
namespace Downkyi.UI.Services;
public interface IMainSearchService
{
/// <summary>
/// 解析支持的输入,
/// 支持的格式有:<para/>
/// av号av170001, AV170001, https://www.bilibili.com/video/av170001 <para/>
/// BV号BV17x411w7KC, https://www.bilibili.com/video/BV17x411w7KC, https://b23.tv/BV17x411w7KC <para/>
/// 番剧电影、电视剧ss号ss32982, SS32982, https://www.bilibili.com/bangumi/play/ss32982 <para/>
/// 番剧电影、电视剧ep号ep317925, EP317925, https://www.bilibili.com/bangumi/play/ep317925 <para/>
/// 番剧电影、电视剧md号md28228367, MD28228367, https://www.bilibili.com/bangumi/media/md28228367 <para/>
/// 课程ss号https://www.bilibili.com/cheese/play/ss205 <para/>
/// 课程ep号https://www.bilibili.com/cheese/play/ep3489 <para/>
/// 收藏夹ml1329019876, ML1329019876, https://www.bilibili.com/medialist/detail/ml1329019876, https://www.bilibili.com/medialist/play/ml1329019876/ <para/>
/// 用户空间uid928123, UID928123, uid:928123, UID:928123, https://space.bilibili.com/928123
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
bool BiliInput(string input);
/// <summary>
/// 搜索关键词
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
bool SearchKey(string input);
}

@ -0,0 +1,140 @@
using Downkyi.Core.Bili.Utils;
using Downkyi.Core.Settings;
using Downkyi.Core.Settings.Models;
using Downkyi.UI.ViewModels.User;
using Downkyi.UI.ViewModels.Video;
namespace Downkyi.UI.Services;
public class MainSearchService : IMainSearchService
{
private readonly INavigationService NavigationService;
public MainSearchService(INavigationService navigationService)
{
NavigationService = navigationService;
}
public bool BiliInput(string input)
{
// 移除剪贴板id
//string validId = input.Replace(AppConstant.ClipboardId, "");
string validId = input;
// 参数
Dictionary<string, object> parameter = new()
{
{ "key", "MainSearchService" },
{ "value", new object() },
};
// 视频
if (ParseEntrance.IsAvId(validId))
{
parameter["value"] = $"{ParseEntrance.VideoUrl}{validId.ToLower()}";
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsAvUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBvId(validId))
{
parameter["value"] = $"{ParseEntrance.VideoUrl}{input}";
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBvUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
// 番剧(电影、电视剧)
else if (ParseEntrance.IsBangumiSeasonId(validId))
{
parameter["value"] = $"{ParseEntrance.BangumiUrl}{input.ToLower()}";
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBangumiSeasonUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBangumiEpisodeId(validId))
{
parameter["value"] = $"{ParseEntrance.BangumiUrl}{input.ToLower()}";
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBangumiEpisodeUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBangumiMediaId(validId))
{
parameter["value"] = $"{ParseEntrance.BangumiMediaUrl}{input.ToLower()}";
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
else if (ParseEntrance.IsBangumiMediaUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
// 课程
else if (ParseEntrance.IsCheeseSeasonUrl(validId)
|| ParseEntrance.IsCheeseEpisodeUrl(validId))
{
parameter["value"] = validId;
NavigationService.ForwardAsync(VideoDetailViewModel.Key, parameter);
}
// 用户参数传入mid
else if (ParseEntrance.IsUserId(validId) || ParseEntrance.IsUserUrl(validId))
{
NavigateToViewUserSpace(ParseEntrance.GetUserId(input));
}
// 收藏夹
else if (ParseEntrance.IsFavoritesId(validId) || ParseEntrance.IsFavoritesUrl(validId))
{
parameter["value"] = ParseEntrance.GetFavoritesId(input);
NavigationService.ForwardAsync(PublicFavoritesViewModel.Key, parameter);
}
else
{
return false;
}
return true;
}
public bool SearchKey(string input)
{
// TODO
return false;
}
/// <summary>
/// 导航到用户空间,
/// 如果传入的mid与本地登录的mid一致
/// 则进入我的用户空间。
/// </summary>
/// <param name="mid"></param>
private void NavigateToViewUserSpace(long mid)
{
Dictionary<string, object> parameter = new()
{
{ "key", "MainSearchService" },
{ "value", mid },
};
UserInfoSettings userInfo = SettingsManager.GetInstance().GetUserInfo();
if (userInfo != null && userInfo.Mid == mid)
{
NavigationService.ForwardAsync(MySpaceViewModel.Key, parameter);
}
else
{
NavigationService.ForwardAsync(UserSpaceViewModel.Key, parameter);
}
}
}

@ -1,15 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Downkyi.Core.Bili; using Downkyi.Core.Bili;
using Downkyi.Core.Log;
using Downkyi.Core.Settings; using Downkyi.Core.Settings;
using Downkyi.Core.Settings.Models; using Downkyi.Core.Settings.Models;
using Downkyi.Core.Storage; using Downkyi.Core.Storage;
using Downkyi.UI.Mvvm; using Downkyi.UI.Mvvm;
using Downkyi.UI.Services;
using Downkyi.UI.ViewModels.DownloadManager; using Downkyi.UI.ViewModels.DownloadManager;
using Downkyi.UI.ViewModels.Login; using Downkyi.UI.ViewModels.Login;
using Downkyi.UI.ViewModels.Settings; using Downkyi.UI.ViewModels.Settings;
using Downkyi.UI.ViewModels.Toolbox; using Downkyi.UI.ViewModels.Toolbox;
using Downkyi.UI.ViewModels.User; using Downkyi.UI.ViewModels.User;
using System.Text.RegularExpressions;
namespace Downkyi.UI.ViewModels; namespace Downkyi.UI.ViewModels;
@ -115,9 +118,29 @@ public partial class IndexViewModel : ViewModelBase
#region 业务逻辑 #region 业务逻辑
/// <summary>
/// 进入B站链接的处理逻辑
/// 只负责处理输入,并跳转到视频详情页。<para/>
/// 不是支持的格式,则进入搜索页面。
/// </summary>
private void EnterBili() private void EnterBili()
{ {
NotificationEvent.Publish("Enter Bili!"); if (InputText == string.Empty)
{
return;
}
Log.Logger.Debug(InputText);
InputText = Regex.Replace(InputText, @"[【]*[^【]*[^】]*[】 ]", "");
bool isSupport = MainSearchService.BiliInput(InputText);
if (!isSupport)
{
// 关键词搜索
MainSearchService.SearchKey(InputText);
}
InputText = string.Empty;
} }
private async Task UpdateUserInfo() private async Task UpdateUserInfo()

@ -114,14 +114,11 @@ public partial class NetworkViewModel : BaseSettingsViewModel
for (int i = 1; i <= 10; i++) { Splits.Add(i); } for (int i = 1; i <= 10; i++) { Splits.Add(i); }
// Aria的日志等级 // Aria的日志等级
AriaLogLevels = new List<string> AriaLogLevels.Add("DEBUG");
{ AriaLogLevels.Add("INFO");
"DEBUG", AriaLogLevels.Add("NOTICE");
"INFO", AriaLogLevels.Add("WARN");
"NOTICE", AriaLogLevels.Add("ERROR");
"WARN",
"ERROR"
};
// Aria同时下载数 // Aria同时下载数
for (int i = 1; i <= 10; i++) { AriaMaxConcurrentDownloads.Add(i); } for (int i = 1; i <= 10; i++) { AriaMaxConcurrentDownloads.Add(i); }
@ -130,12 +127,9 @@ public partial class NetworkViewModel : BaseSettingsViewModel
for (int i = 1; i <= 10; i++) { AriaSplits.Add(i); } for (int i = 1; i <= 10; i++) { AriaSplits.Add(i); }
// Aria文件预分配 // Aria文件预分配
AriaFileAllocations = new List<string> AriaFileAllocations.Add("NONE");
{ AriaFileAllocations.Add("PREALLOC");
"NONE", AriaFileAllocations.Add("FALLOC");
"PREALLOC",
"FALLOC"
};
#endregion #endregion
} }
@ -288,10 +282,8 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetMaxCurrentDownloads(object parameter) private void SetMaxCurrentDownloads()
{ {
SelectedMaxCurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedMaxCurrentDownload); bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedMaxCurrentDownload);
PublishTip(Key, isSucceed); PublishTip(Key, isSucceed);
} }
@ -301,10 +293,8 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetSplits(object parameter) private void SetSplits()
{ {
SelectedSplit = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetSplit(SelectedSplit); bool isSucceed = SettingsManager.GetInstance().SetSplit(SelectedSplit);
PublishTip(Key, isSucceed); PublishTip(Key, isSucceed);
} }
@ -389,9 +379,9 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetAriaLogLevels(string parameter) private void SetAriaLogLevels()
{ {
var ariaLogLevel = parameter switch var ariaLogLevel = SelectedAriaLogLevel switch
{ {
"DEBUG" => AriaConfigLogLevel.DEBUG, "DEBUG" => AriaConfigLogLevel.DEBUG,
"INFO" => AriaConfigLogLevel.INFO, "INFO" => AriaConfigLogLevel.INFO,
@ -409,10 +399,8 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetAriaMaxConcurrentDownloads(object parameter) private void SetAriaMaxConcurrentDownloads()
{ {
SelectedAriaMaxConcurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedAriaMaxConcurrentDownload); bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedAriaMaxConcurrentDownload);
PublishTip(Key, isSucceed); PublishTip(Key, isSucceed);
} }
@ -422,10 +410,8 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetAriaSplits(object parameter) private void SetAriaSplits()
{ {
SelectedAriaSplit = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetAriaSplit(SelectedAriaSplit); bool isSucceed = SettingsManager.GetInstance().SetAriaSplit(SelectedAriaSplit);
PublishTip(Key, isSucceed); PublishTip(Key, isSucceed);
} }
@ -498,11 +484,10 @@ public partial class NetworkViewModel : BaseSettingsViewModel
/// <summary> /// <summary>
/// Aria文件预分配事件 /// Aria文件预分配事件
/// </summary> /// </summary>
/// <param name="parameter"></param>
[RelayCommand] [RelayCommand]
private void SetAriaFileAllocations(string parameter) private void SetAriaFileAllocations()
{ {
var ariaFileAllocation = parameter switch var ariaFileAllocation = SelectedAriaFileAllocation switch
{ {
"NONE" => AriaConfigFileAllocation.NONE, "NONE" => AriaConfigFileAllocation.NONE,
"PREALLOC" => AriaConfigFileAllocation.PREALLOC, "PREALLOC" => AriaConfigFileAllocation.PREALLOC,

@ -0,0 +1,29 @@
using CommunityToolkit.Mvvm.Input;
using Downkyi.UI.Mvvm;
namespace Downkyi.UI.ViewModels.Video;
public partial class PublicFavoritesViewModel : ViewModelBase
{
public const string Key = "PublicFavorites";
public PublicFavoritesViewModel(BaseServices baseServices) : base(baseServices)
{
}
#region 命令申明
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task BackwardAsync()
{
Dictionary<string, object> parameter = new()
{
{ "key", Key },
};
await NavigationService.BackwardAsync(parameter);
}
#endregion
}

@ -0,0 +1,29 @@
using CommunityToolkit.Mvvm.Input;
using Downkyi.UI.Mvvm;
namespace Downkyi.UI.ViewModels.Video;
public partial class VideoDetailViewModel : ViewModelBase
{
public const string Key = "VideoDetail";
public VideoDetailViewModel(BaseServices baseServices) : base(baseServices)
{
}
#region 命令申明
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task BackwardAsync()
{
Dictionary<string, object> parameter = new()
{
{ "key", Key },
};
await NavigationService.BackwardAsync(parameter);
}
#endregion
}

@ -9,6 +9,7 @@ using Downkyi.UI.ViewModels.Login;
using Downkyi.UI.ViewModels.Settings; using Downkyi.UI.ViewModels.Settings;
using Downkyi.UI.ViewModels.Toolbox; using Downkyi.UI.ViewModels.Toolbox;
using Downkyi.UI.ViewModels.User; using Downkyi.UI.ViewModels.User;
using Downkyi.UI.ViewModels.Video;
using Downkyi.ViewModels; using Downkyi.ViewModels;
using Downkyi.ViewModels.Login; using Downkyi.ViewModels.Login;
using Downkyi.ViewModels.Settings; using Downkyi.ViewModels.Settings;
@ -92,31 +93,41 @@ public static class ServiceLocator
.AddScoped<BaseServices>() .AddScoped<BaseServices>()
.AddSingleton<IBroadcastEvent, BroadcastEvent>() .AddSingleton<IBroadcastEvent, BroadcastEvent>()
.AddSingleton<INotificationEvent, NotificationEvent>() .AddSingleton<INotificationEvent, NotificationEvent>()
.AddSingleton<IClipboardService, ClipboardService>()
.AddSingleton<IDictionaryResource, DictionaryResource>() .AddSingleton<IDictionaryResource, DictionaryResource>()
.AddSingleton<IMainSearchService, MainSearchService>()
.AddSingleton<INavigationService, NavigationService>() .AddSingleton<INavigationService, NavigationService>()
.AddSingleton<IStoragePicker, StoragePicker>() .AddSingleton<IStoragePicker, StoragePicker>()
.AddSingleton<IStorageService, StorageService>() .AddSingleton<IStorageService, StorageService>()
//ViewModels //ViewModels
.AddSingleton<MainWindowViewModel>() .AddSingleton<MainWindowViewModel>()
.AddSingleton<IndexViewModel>() .AddSingleton<IndexViewModel>()
//
.AddSingleton<DownloadManagerViewModel>()
.AddSingleton<DownloadingViewModel>()
.AddSingleton<DownloadFinishedViewModel>()
//
.AddSingleton<LoginViewModel>() .AddSingleton<LoginViewModel>()
.AddSingleton<QRCodeViewModelProxy>() .AddSingleton<QRCodeViewModelProxy>()
.AddSingleton<CookiesViewModel>() .AddSingleton<CookiesViewModel>()
.AddSingleton<MySpaceViewModel>() //
.AddSingleton<UserSpaceViewModel>()
.AddSingleton<SettingsViewModel>() .AddSingleton<SettingsViewModel>()
.AddSingleton<BasicViewModel>() .AddSingleton<BasicViewModel>()
.AddSingleton<NetworkViewModel>() .AddSingleton<NetworkViewModel>()
.AddSingleton<VideoViewModelProxy>() .AddSingleton<VideoViewModelProxy>()
.AddSingleton<DanmakuViewModelProxy>() .AddSingleton<DanmakuViewModelProxy>()
.AddSingleton<AboutViewModelProxy>() .AddSingleton<AboutViewModelProxy>()
.AddSingleton<DownloadManagerViewModel>() //
.AddSingleton<DownloadingViewModel>()
.AddSingleton<DownloadFinishedViewModel>()
.AddSingleton<ToolboxViewModel>() .AddSingleton<ToolboxViewModel>()
.AddSingleton<BiliHelperViewModel>() .AddSingleton<BiliHelperViewModel>()
.AddSingleton<DelogoViewModelProxy>() .AddSingleton<DelogoViewModelProxy>()
.AddSingleton<ExtractMediaViewModelProxy>() .AddSingleton<ExtractMediaViewModelProxy>()
//
.AddSingleton<MySpaceViewModel>()
.AddSingleton<UserSpaceViewModel>()
//
.AddSingleton<VideoDetailViewModel>()
.AddSingleton<PublicFavoritesViewModel>()
.BuildServiceProvider()); .BuildServiceProvider());
} }
} }

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Downkyi.UI.Services;
using System;
using System.Threading.Tasks;
namespace Downkyi.Services;
public class ClipboardService : IClipboardService
{
public async Task<string> GetTextAsync()
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
desktop.MainWindow?.Clipboard is not { } provider)
throw new NullReferenceException("Missing Clipboard instance.");
return await provider.GetTextAsync() ?? string.Empty;
}
}

@ -1,5 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Downkyi.Core.Settings;
using Downkyi.Core.Settings.Enum;
using Downkyi.Core.Utils; using Downkyi.Core.Utils;
using Downkyi.UI.Mvvm; using Downkyi.UI.Mvvm;
using Downkyi.UI.ViewModels; using Downkyi.UI.ViewModels;
@ -8,8 +13,10 @@ using Downkyi.UI.ViewModels.Login;
using Downkyi.UI.ViewModels.Settings; using Downkyi.UI.ViewModels.Settings;
using Downkyi.UI.ViewModels.Toolbox; using Downkyi.UI.ViewModels.Toolbox;
using Downkyi.UI.ViewModels.User; using Downkyi.UI.ViewModels.User;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Downkyi.ViewModels; namespace Downkyi.ViewModels;
@ -19,6 +26,8 @@ public partial class MainWindowViewModel : ViewModelBase
private readonly LinkStack<string> _pages = new(); private readonly LinkStack<string> _pages = new();
private readonly CancellationTokenSource? tokenSource;
#region 页面属性申明 #region 页面属性申明
[ObservableProperty] [ObservableProperty]
@ -55,8 +64,61 @@ public partial class MainWindowViewModel : ViewModelBase
MessageVisibility = false; MessageVisibility = false;
}); });
// 监听剪贴板线程
string oldClip = string.Empty;
Task.Run(async () =>
{
CancellationToken cancellationToken = tokenSource!.Token;
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop ||
desktop.MainWindow?.Clipboard is not { } provider)
throw new NullReferenceException("Missing Clipboard instance.");
await provider.SetTextAsync(oldClip);
while (true)
{
AllowStatus isListenClipboard = SettingsManager.GetInstance().IsListenClipboard();
if (isListenClipboard != AllowStatus.YES)
{
continue;
}
// 判断是否该结束线程若为true跳出while循环
if (cancellationToken.IsCancellationRequested)
{
break;
}
else
{
string clip = await ClipboardService.GetTextAsync();
if (clip.Equals(oldClip))
{
await Task.Delay(100);
continue;
}
oldClip = clip;
MainSearchService.BiliInput(clip);
}
}
}, (tokenSource = new CancellationTokenSource()).Token);
} }
#region 命令申明
/// <summary>
/// 退出窗口时执行
/// </summary>
[RelayCommand]
private void OnClosing()
{
// 取消任务
tokenSource?.Cancel();
}
#endregion
public void Forward(string viewKey, Dictionary<string, object>? parameter = null) public void Forward(string viewKey, Dictionary<string, object>? parameter = null)
{ {
var viewModel = SetContent(viewKey); var viewModel = SetContent(viewKey);

@ -2,6 +2,8 @@
x:Class="Downkyi.Views.MainWindow" x:Class="Downkyi.Views.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
xmlns:local="using:Downkyi" xmlns:local="using:Downkyi"
xmlns:vm="using:Downkyi.ViewModels" xmlns:vm="using:Downkyi.ViewModels"
Title="{DynamicResource AppName}" Title="{DynamicResource AppName}"
@ -14,6 +16,11 @@
Design.DataContext="{x:Static local:ServiceLocator.MainWindowViewModel}" Design.DataContext="{x:Static local:ServiceLocator.MainWindowViewModel}"
Icon="/Assets/logo.ico" Icon="/Assets/logo.ico"
WindowStartupLocation="CenterScreen"> WindowStartupLocation="CenterScreen">
<i:Interaction.Behaviors>
<ia:EventTriggerBehavior EventName="Closing">
<ia:InvokeCommandAction Command="{Binding ClosingCommand}" />
</ia:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Grid> <Grid>
<ContentControl Content="{Binding Content}" /> <ContentControl Content="{Binding Content}" />

@ -87,15 +87,14 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource MaxCurrentDownloads}" /> Text="{DynamicResource MaxCurrentDownloads}" />
<ComboBox <ComboBox
Name="maxCurrentDownloads"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
ItemsSource="{Binding MaxCurrentDownloads}" ItemsSource="{Binding MaxCurrentDownloads}"
SelectedValue="{Binding SelectedMaxCurrentDownload}"> SelectedItem="{Binding SelectedMaxCurrentDownload}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedMaxCurrentDownload}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedMaxCurrentDownload}">
<ia:InvokeCommandAction Command="{Binding SetMaxCurrentDownloadsCommand}" CommandParameter="{Binding ElementName=maxCurrentDownloads, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetMaxCurrentDownloadsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>
@ -107,15 +106,14 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource Split}" /> Text="{DynamicResource Split}" />
<ComboBox <ComboBox
Name="splits"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
ItemsSource="{Binding Splits}" ItemsSource="{Binding Splits}"
SelectedValue="{Binding SelectedSplit}"> SelectedItem="{Binding SelectedSplit}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedSplit}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedSplit}">
<ia:InvokeCommandAction Command="{Binding SetSplitsCommand}" CommandParameter="{Binding ElementName=splits, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetSplitsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>
@ -203,15 +201,14 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource AriaLogLevel}" /> Text="{DynamicResource AriaLogLevel}" />
<ComboBox <ComboBox
Name="ariaLogLevels"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
ItemsSource="{Binding AriaLogLevels}" ItemsSource="{Binding AriaLogLevels}"
SelectedValue="{Binding SelectedAriaLogLevel}"> SelectedItem="{Binding SelectedAriaLogLevel}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaLogLevel}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaLogLevel}">
<ia:InvokeCommandAction Command="{Binding SetAriaLogLevelsCommand}" CommandParameter="{Binding ElementName=ariaLogLevels, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetAriaLogLevelsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>
@ -223,15 +220,14 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource AriaMaxConcurrentDownloads}" /> Text="{DynamicResource AriaMaxConcurrentDownloads}" />
<ComboBox <ComboBox
Name="ariaMaxConcurrentDownloads"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
ItemsSource="{Binding AriaMaxConcurrentDownloads}" ItemsSource="{Binding AriaMaxConcurrentDownloads}"
SelectedValue="{Binding SelectedAriaMaxConcurrentDownload}"> SelectedItem="{Binding SelectedAriaMaxConcurrentDownload}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaMaxConcurrentDownload}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaMaxConcurrentDownload}">
<ia:InvokeCommandAction Command="{Binding SetAriaMaxConcurrentDownloadsCommand}" CommandParameter="{Binding ElementName=ariaMaxConcurrentDownloads, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetAriaMaxConcurrentDownloadsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>
@ -243,15 +239,14 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource AriaSplit}" /> Text="{DynamicResource AriaSplit}" />
<ComboBox <ComboBox
Name="ariaSplits"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
ItemsSource="{Binding AriaSplits}" ItemsSource="{Binding AriaSplits}"
SelectedValue="{Binding SelectedAriaSplit}"> SelectedItem="{Binding SelectedAriaSplit}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaSplit}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaSplit}">
<ia:InvokeCommandAction Command="{Binding SetAriaSplitsCommand}" CommandParameter="{Binding ElementName=ariaSplits, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetAriaSplitsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>
@ -376,7 +371,6 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{DynamicResource AriaFileAllocation}" /> Text="{DynamicResource AriaFileAllocation}" />
<ComboBox <ComboBox
Name="ariaFileAllocations"
Width="100" Width="100"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Classes="normal" Classes="normal"
@ -384,7 +378,7 @@
SelectedValue="{Binding SelectedAriaFileAllocation}"> SelectedValue="{Binding SelectedAriaFileAllocation}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaFileAllocation}"> <iac:ValueChangedTriggerBehavior Binding="{Binding SelectedAriaFileAllocation}">
<ia:InvokeCommandAction Command="{Binding SetAriaFileAllocationsCommand}" CommandParameter="{Binding ElementName=ariaFileAllocations, Path=SelectedValue}" /> <ia:InvokeCommandAction Command="{Binding SetAriaFileAllocationsCommand}" />
</iac:ValueChangedTriggerBehavior> </iac:ValueChangedTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</ComboBox> </ComboBox>

Loading…
Cancel
Save