保存工作进度

This commit is contained in:
leiurayer 2024-08-01 22:28:38 +08:00
parent 1ca966f617
commit e5310718ab
48 changed files with 792 additions and 97 deletions

View File

@ -2,7 +2,7 @@
public static class BiliLocator
{
private static ILogin _login;
private static ILogin? _login;
public static ILogin Login
{
get
@ -12,7 +12,7 @@ public static class BiliLocator
}
}
private static IUser _user;
private static IUser? _user;
public static IUser User
{
get
@ -21,4 +21,18 @@ public static class BiliLocator
return _user;
}
}
private static IVideo? _video;
public static IVideo Video(string input)
{
_video ??= new Web.Video(input);
if (_video.Input() != input)
{
_video = new Web.Video(input);
}
return _video;
}
}

View File

@ -23,9 +23,9 @@ public static class Cookies
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static CookieContainer ReadCookiesFromDisk(string file)
public static CookieContainer? ReadCookiesFromDisk(string file)
{
return (CookieContainer)ObjectHelper.ReadObjectFromDisk(file);
return (CookieContainer?)ObjectHelper.ReadObjectFromDisk(file);
}
/// <summary>
@ -37,16 +37,16 @@ public static class Cookies
{
var lstCookies = new List<Cookie>();
Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable",
var table = (Hashtable?)cc.GetType().InvokeMember("m_domainTable",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField |
System.Reflection.BindingFlags.Instance, null, cc, Array.Empty<object>());
foreach (object pathList in table.Values)
foreach (object pathList in table!.Values)
{
SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list",
var lstCookieCol = (SortedList?)pathList.GetType().InvokeMember("m_list",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField
| System.Reflection.BindingFlags.Instance, null, pathList, Array.Empty<object>());
foreach (CookieCollection colCookies in lstCookieCol.Values)
foreach (CookieCollection colCookies in lstCookieCol!.Values)
{
foreach (Cookie c in colCookies.Cast<Cookie>())
{
@ -92,7 +92,7 @@ public static class Cookies
if (strList2.Length == 0) { return cookieContainer; }
// 获取expires
string expires = strList2.FirstOrDefault(it => it.Contains("Expires")).Split('=')[1];
string expires = strList2.FirstOrDefault(it => it.Contains("Expires"))!.Split('=')[1];
DateTime dateTime = DateTime.Now;
dateTime = dateTime.AddSeconds(int.Parse(expires));

View File

@ -8,18 +8,18 @@ public interface ILogin
/// 申请二维码
/// </summary>
/// <returns>(url, key)</returns>
Tuple<string, string> GetQRCodeUrl();
Tuple<string, string>? GetQRCodeUrl();
/// <summary>
/// 扫码登录
/// </summary>
/// <param name="qrcodeKey"></param>
/// <returns></returns>
QRCodeStatus PollQRCode(string qrcodeKey);
QRCodeStatus? PollQRCode(string qrcodeKey);
/// <summary>
/// 导航栏用户信息
/// </summary>
/// <returns></returns>
NavigationInfo GetNavigationInfo();
NavigationInfo? GetNavigationInfo();
}

View File

@ -0,0 +1,10 @@
using Downkyi.Core.Bili.Models;
namespace Downkyi.Core.Bili;
public interface IVideo
{
string Input();
VideoInfo? GetVideoInfo(string? bvid = null, long aid = -1);
}

View File

@ -3,8 +3,8 @@
public class NavigationInfo
{
public long Mid { get; set; }
public string Name { get; set; }
public string Header { get; set; }
public string Name { get; set; } = string.Empty;
public string Header { get; set; } = string.Empty;
public int VipStatus { get; set; } // 会员开通状态 // 01
public bool IsLogin { get; set; }
}

View File

@ -2,8 +2,8 @@
public class QRCodeStatus
{
public string Url { get; set; }
public string Token { get; set; }
public string Url { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
public long Timestamp { get; set; }
/// <summary>
@ -13,5 +13,5 @@ public class QRCodeStatus
/// 86101未扫码
/// </summary>
public long Code { get; set; }
public string Message { get; set; }
public string Message { get; set; } = string.Empty;
}

View File

@ -2,6 +2,6 @@
public class Quality
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public int Id { get; set; }
}

View File

@ -0,0 +1,11 @@
namespace Downkyi.Core.Bili.Models;
public class VideoInfo
{
public long Aid { get; set; }
public string Bvid { get; set; } = string.Empty;
public long Cid { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string PublishTime { get; set; } = string.Empty;
}

View File

@ -459,7 +459,7 @@ public static class ParseEntrance
/// <returns></returns>
private static string EnableHttps(string url)
{
if (!IsUrl(url)) { return null; }
if (!IsUrl(url)) { return string.Empty; }
return url.Replace("http://", "https://");
}

View File

@ -1,17 +1,17 @@
using BiliSharp;
using BiliSharp.Api.Login;
using Downkyi.BiliSharp;
using Downkyi.BiliSharp.Api.Login;
using Downkyi.Core.Bili.Models;
using Downkyi.Core.Settings;
namespace Downkyi.Core.Bili.Web;
public class Login : ILogin
internal class Login : ILogin
{
/// <summary>
/// 申请二维码(web端)
/// </summary>
/// <returns>(url, key)</returns>
public Tuple<string, string> GetQRCodeUrl()
public Tuple<string, string>? GetQRCodeUrl()
{
string userAgent = SettingsManager.GetInstance().GetUserAgent();
BiliManager.Instance().SetUserAgent(userAgent);
@ -26,7 +26,7 @@ public class Login : ILogin
/// 扫码登录(web端)
/// </summary>
/// <returns></returns>
public QRCodeStatus PollQRCode(string qrcodeKey)
public QRCodeStatus? PollQRCode(string qrcodeKey)
{
string userAgent = SettingsManager.GetInstance().GetUserAgent();
BiliManager.Instance().SetUserAgent(userAgent);
@ -48,7 +48,7 @@ public class Login : ILogin
/// 导航栏用户信息
/// </summary>
/// <returns></returns>
public NavigationInfo GetNavigationInfo()
public NavigationInfo? GetNavigationInfo()
{
string userAgent = SettingsManager.GetInstance().GetUserAgent();
BiliManager.Instance().SetUserAgent(userAgent);

View File

@ -58,7 +58,7 @@ public static class LoginHelper
/// 获得登录的cookies
/// </summary>
/// <returns></returns>
public static CookieContainer GetLoginInfoCookies()
public static CookieContainer? GetLoginInfoCookies()
{
string tempFile = LOCAL_LOGIN_INFO + "-" + Guid.NewGuid().ToString("N");
@ -80,7 +80,7 @@ public static class LoginHelper
}
else { return null; }
CookieContainer cookies = Cookies.ReadCookiesFromDisk(tempFile);
var cookies = Cookies.ReadCookiesFromDisk(tempFile);
if (File.Exists(tempFile))
{

View File

@ -1,5 +1,5 @@
namespace Downkyi.Core.Bili.Web;
public class User : IUser
internal class User : IUser
{
}

View File

@ -0,0 +1,71 @@
using Downkyi.BiliSharp.Api.Models.Video;
using Downkyi.Core.Bili.Models;
using Downkyi.Core.Bili.Utils;
namespace Downkyi.Core.Bili.Web;
internal class Video : IVideo
{
private readonly VideoView? videoView;
private readonly string _input = string.Empty;
internal Video(string input)
{
if (input == null)
{
return;
}
_input = input;
if (ParseEntrance.IsAvId(input) || ParseEntrance.IsAvUrl(input))
{
long avid = ParseEntrance.GetAvId(input);
videoView = BiliSharp.Api.Video.VideoInfo.GetVideoViewInfo(null, avid);
}
if (ParseEntrance.IsBvId(input) || ParseEntrance.IsBvUrl(input))
{
string bvid = ParseEntrance.GetBvId(input);
videoView = BiliSharp.Api.Video.VideoInfo.GetVideoViewInfo(bvid);
}
}
public string Input()
{
return _input;
}
/// <summary>
/// 获取视频详情页信息
/// </summary>
/// <param name="bvid"></param>
/// <param name="aid"></param>
/// <returns></returns>
public VideoInfo? GetVideoInfo(string? bvid = null, long aid = -1)
{
if (videoView == null) { return null; }
if (videoView.Data == null) { return null; }
if (videoView.Data.View == null) { return null; }
/* 视频发布时间 */
// 当地时区
DateTime startTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local);
DateTime dateTime = startTime.AddSeconds(videoView.Data.View.Pubdate);
string publishTime = dateTime.ToString("yyyy-MM-dd HH:mm:ss");
VideoInfo videoInfo = new()
{
Aid = videoView.Data.View.Aid,
Bvid = videoView.Data.View.Bvid,
Cid = videoView.Data.View.Cid,
Title = videoView.Data.View.Title,
Description = videoView.Data.View.Desc,
PublishTime = publishTime,
};
return videoInfo;
}
}

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@ -10,14 +10,10 @@
<ItemGroup>
<PackageReference Include="Aria2cNet" Version="1.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="Downkyi.BiliSharp" Version="0.0.3" />
<PackageReference Include="NLog" Version="5.3.2" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BiliSharp\BiliSharp.csproj" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.8" />
</ItemGroup>
</Project>

View File

@ -3,6 +3,13 @@ using System.Net;
namespace Downkyi.Core.Downloader;
#pragma warning disable CS8601 // 引用类型赋值可能为 null。
#pragma warning disable CS8602 // 解引用可能出现空引用。
#pragma warning disable CS8604 // 引用类型参数可能为 null。
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
#pragma warning disable CS8622 // 参数类型中引用类型的为 Null 性与目标委托不匹配(可能是由于为 Null 性特性)。
#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
/// <summary>
/// 文件合并改变事件
/// </summary>
@ -170,7 +177,7 @@ public class MultiThreadDownloader
/// </summary>
/// <param name="sourceUrl"></param>
/// <param name="numOfParts"></param>
public MultiThreadDownloader(string sourceUrl, int numOfParts) : this(sourceUrl, null, numOfParts)
public MultiThreadDownloader(string sourceUrl, int numOfParts) : this(sourceUrl, null, numOfParts)
{
}
@ -361,6 +368,7 @@ public class MultiThreadDownloader
public long GetContentLength(ref bool rangeAllowed, ref string redirectedUrl)
{
_request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36";
_request.ServicePoint.ConnectionLimit = 4;
_requestConfigure(_request);
@ -438,4 +446,11 @@ public class MultiThreadDownloader
}
#endregion
}
}
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
#pragma warning restore CS8622 // 参数类型中引用类型的为 Null 性与目标委托不匹配(可能是由于为 Null 性特性)。
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
#pragma warning restore CS8604 // 引用类型参数可能为 null。
#pragma warning restore CS8602 // 解引用可能出现空引用。
#pragma warning restore CS8601 // 引用类型赋值可能为 null。

View File

@ -4,6 +4,8 @@ using System.Net;
namespace Downkyi.Core.Downloader;
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
/// <summary>
/// 部分下载器
/// </summary>
@ -259,4 +261,6 @@ public class PartialDownloader
{
_wait = false;
}
}
}
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。

View File

@ -210,7 +210,7 @@ public static class FFmpegHelper
/// <param name="arg">参数</param>
/// <param name="workingDirectory">工作路径</param>
/// <param name="output">输出重定向</param>
private static void ExcuteProcess(string exe, string arg, string workingDirectory, DataReceivedEventHandler output)
private static void ExcuteProcess(string exe, string arg, string? workingDirectory, DataReceivedEventHandler output)
{
using var p = new Process();
p.StartInfo.FileName = exe;

View File

@ -13,7 +13,7 @@ public class DanmakuSettings
public AllowStatus IsCustomDanmakuResolution { get; set; } = AllowStatus.NONE;
public int DanmakuScreenWidth { get; set; } = -1;
public int DanmakuScreenHeight { get; set; } = -1;
public string DanmakuFontName { get; set; } = null;
public string? DanmakuFontName { get; set; } = null;
public int DanmakuFontSize { get; set; } = -1;
public int DanmakuLineCount { get; set; } = -1;
public DanmakuLayoutAlgorithm DanmakuLayoutAlgorithm { get; set; } = DanmakuLayoutAlgorithm.NONE;

View File

@ -19,13 +19,13 @@ public class NetworkSettings
#region built-in
public int Split { get; set; } = -1;
public AllowStatus IsHttpProxy { get; set; } = AllowStatus.NONE;
public string HttpProxy { get; set; } = null;
public string? HttpProxy { get; set; } = null;
public int HttpProxyListenPort { get; set; } = -1;
#endregion
#region Aria
public string AriaToken { get; set; } = null;
public string AriaHost { get; set; } = null;
public string? AriaToken { get; set; } = null;
public string? AriaHost { get; set; } = null;
public int AriaListenPort { get; set; } = -1;
public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET;
public int AriaSplit { get; set; } = -1;
@ -34,7 +34,7 @@ public class NetworkSettings
public AriaConfigFileAllocation AriaFileAllocation { get; set; } = AriaConfigFileAllocation.NOT_SET;
public AllowStatus IsAriaHttpProxy { get; set; } = AllowStatus.NONE;
public string AriaHttpProxy { get; set; } = null;
public string? AriaHttpProxy { get; set; } = null;
public int AriaHttpProxyListenPort { get; set; } = -1;
#endregion
}

View File

@ -3,7 +3,7 @@
public class UserInfoSettings
{
public long Mid { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsLogin { get; set; } // 是否登录
public bool IsVip { get; set; } // 是否为大会员未登录时为false
}

View File

@ -12,11 +12,11 @@ public class VideoSettings
public int Quality { get; set; } = -1; // 画质
public int AudioQuality { get; set; } = -1; // 音质
public AllowStatus IsTranscodingFlvToMp4 { get; set; } = AllowStatus.NONE; // 是否将flv转为mp4
public string SaveVideoRootPath { get; set; } = null; // 视频保存路径
public List<string> HistoryVideoRootPaths { get; set; } = null; // 历史视频保存路径
public string? SaveVideoRootPath { get; set; } = null; // 视频保存路径
public List<string>? HistoryVideoRootPaths { get; set; } = null; // 历史视频保存路径
public AllowStatus IsUseSaveVideoRootPath { get; set; } = AllowStatus.NONE; // 是否使用默认视频保存路径
public VideoContentSettings VideoContent { get; set; } = null; // 下载内容
public List<FileNamePart> FileNameParts { get; set; } = null; // 文件命名格式
public string FileNamePartTimeFormat { get; set; } = null; // 文件命名中的时间格式
public VideoContentSettings? VideoContent { get; set; } = null; // 下载内容
public List<FileNamePart>? FileNameParts { get; set; } = null; // 文件命名格式
public string? FileNamePartTimeFormat { get; set; } = null; // 文件命名中的时间格式
public OrderFormat OrderFormat { get; set; } = OrderFormat.NOT_SET; // 文件命名中的序号格式
}

View File

@ -289,7 +289,7 @@ public partial class SettingsManager
/// </summary>
/// <param name="historyPaths"></param>
/// <returns></returns>
public bool SetFileNameParts(List<FileNamePart> fileNameParts)
public bool SetFileNameParts(List<FileNamePart>? fileNameParts)
{
appSettings.Video.FileNameParts = fileNameParts;
return SetSettings();

View File

@ -10,7 +10,7 @@ namespace Downkyi.Core.Settings;
public partial class SettingsManager
{
private static SettingsManager instance;
private static SettingsManager? instance;
// 内存中保存一份配置
private AppSettings appSettings;
@ -41,7 +41,7 @@ public partial class SettingsManager
/// </summary>
private SettingsManager()
{
appSettings = GetSettings();
appSettings = GetSettings() ?? new AppSettings();
}
/// <summary>
@ -67,7 +67,7 @@ public partial class SettingsManager
jsonWordTemplate = Encryptor.DecryptString(jsonWordTemplate, password);
#endif
return JsonConvert.DeserializeObject<AppSettings>(jsonWordTemplate);
return JsonConvert.DeserializeObject<AppSettings>(jsonWordTemplate) ?? new AppSettings();
}
catch (Exception e)
{

View File

@ -10,7 +10,7 @@ public static class Hash
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string GetMd5Hash(string input)
public static string? GetMd5Hash(string input)
{
if (input == null)
{

View File

@ -4,7 +4,7 @@
public class LinkStack<T>
{
//栈顶指示器
public Node<T> Top { get; set; }
public Node<T>? Top { get; set; }
//栈中结点的个数
public int NCount { get; set; }
@ -49,36 +49,36 @@ public class LinkStack<T>
}
//出栈
public T Pop()
public T? Pop()
{
if (IsEmpty())
{
return default;
}
Node<T> p = Top;
Top = Top.Next;
Node<T>? p = Top;
Top = Top!.Next;
--NCount;
return p.Data;
return p!.Data;
}
//
public T Peek()
public T? Peek()
{
if (IsEmpty())
{
return default;
}
return Top.Data;
return Top!.Data;
}
}
//结点定义
public class Node<T>
{
public T Data;
public T? Data;
public Node<T> Next;
public Node<T>? Next;
public Node(T item)
{

View File

@ -27,7 +27,7 @@ public static class ListHelper
/// <param name="item"></param>
public static void AddUnique<T>(List<T> list, T item)
{
if (!list.Exists(t => t.Equals(item)))
if (!list.Exists(t => t!.Equals(item)))
{
list.Add(item);
}
@ -42,7 +42,7 @@ public static class ListHelper
/// <param name="index"></param>
public static void InsertUnique<T>(List<T> list, T item, int index)
{
if (!list.Exists(t => t.Equals(item)))
if (!list.Exists(t => t!.Equals(item)))
{
list.Insert(index, item);
}

View File

@ -15,8 +15,8 @@ public static class ObjectHelper
try
{
using Stream stream = File.Create(file);
var formatter = new BinaryFormatter();
#pragma warning disable SYSLIB0011 // 类型或成员已过时
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
#pragma warning restore SYSLIB0011 // 类型或成员已过时
@ -39,13 +39,13 @@ public static class ObjectHelper
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static object ReadObjectFromDisk(string file)
public static object? ReadObjectFromDisk(string file)
{
try
{
using Stream stream = File.Open(file, FileMode.Open);
var formatter = new BinaryFormatter();
#pragma warning disable SYSLIB0011 // 类型或成员已过时
var formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
#pragma warning restore SYSLIB0011 // 类型或成员已过时
}

View File

@ -25,7 +25,7 @@ public class Stack<T>
_data[++_top] = value;
}
public T Pop()
public T? Pop()
{
if (IsEmpty)
{
@ -35,7 +35,7 @@ public class Stack<T>
return _data[_top--];
}
public T Peek()
public T? Peek()
{
if (IsEmpty)
{

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -0,0 +1,54 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Downkyi.UI.Models
{
public partial class VideoInfoView : ObservableObject
{
public string CoverUrl { get; set; } = string.Empty;
public long UpperMid { get; set; }
public int TypeId { get; set; }
[ObservableProperty]
private string _cover = string.Empty;
[ObservableProperty]
private string _title = string.Empty;
[ObservableProperty]
private string _videoZone = string.Empty;
[ObservableProperty]
private string _createTime = string.Empty;
[ObservableProperty]
private string _playNumber = string.Empty;
[ObservableProperty]
private string _danmakuNumber = string.Empty;
[ObservableProperty]
private string _likeNumber = string.Empty;
[ObservableProperty]
private string _coinNumber = string.Empty;
[ObservableProperty]
private string _favoriteNumber = string.Empty;
[ObservableProperty]
private string _shareNumber = string.Empty;
[ObservableProperty]
private string _replyNumber = string.Empty;
[ObservableProperty]
private string _description = string.Empty;
[ObservableProperty]
private string _upName = string.Empty;
[ObservableProperty]
private string _upHeader = string.Empty;
}
}

View File

@ -0,0 +1,8 @@
using Downkyi.UI.Models;
namespace Downkyi.UI.Services;
public interface IVideoInfoService
{
VideoInfoView? GetVideoView(string input);
}

View File

@ -19,7 +19,7 @@ public class MainSearchService : IMainSearchService
{
// 移除剪贴板id
//string validId = input.Replace(AppConstant.ClipboardId, "");
string validId = input;
// 参数

View File

@ -0,0 +1,21 @@
using Downkyi.Core.Bili;
using Downkyi.UI.Models;
namespace Downkyi.UI.Services;
public class VideoInfoService : IVideoInfoService
{
public VideoInfoView? GetVideoView(string input)
{
if (input == null) { return null; }
var video = BiliLocator.Video(input);
var videoInfo = video.GetVideoInfo();
throw new NotImplementedException();
}
}

View File

@ -6,7 +6,6 @@ using Downkyi.Core.Settings;
using Downkyi.Core.Settings.Models;
using Downkyi.Core.Storage;
using Downkyi.UI.Mvvm;
using Downkyi.UI.Services;
using Downkyi.UI.ViewModels.DownloadManager;
using Downkyi.UI.ViewModels.Login;
using Downkyi.UI.ViewModels.Settings;

View File

@ -28,12 +28,12 @@ public class BaseSettingsViewModel : ViewModelBase
// 发送通知
if (isSucceed)
{
NotificationEvent.Publish(TipSettingUpdated);
//NotificationEvent.Publish(TipSettingUpdated);
Log.Logger.Info($"{key}: {TipSettingUpdated}");
}
else
{
NotificationEvent.Publish(TipSettingFailed);
//NotificationEvent.Publish(TipSettingFailed);
Log.Logger.Info($"{key}: {TipSettingFailed}");
}
}

View File

@ -1,5 +1,8 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Downkyi.UI.Models;
using Downkyi.UI.Mvvm;
using Downkyi.UI.ViewModels.DownloadManager;
namespace Downkyi.UI.ViewModels.Video;
@ -7,8 +10,42 @@ public partial class VideoDetailViewModel : ViewModelBase
{
public const string Key = "VideoDetail";
// 保存输入字符串,避免被用户修改
private string? input = null;
#region
[ObservableProperty]
private string _inputText = string.Empty;
[ObservableProperty]
private bool _loadingVisibility;
[ObservableProperty]
private bool _contentVisibility;
[ObservableProperty]
private VideoInfoView _videoInfoView = new();
#endregion
public VideoDetailViewModel(BaseServices baseServices) : base(baseServices)
{
#region
ContentVisibility = true;
VideoInfoView.Title = "video name";
VideoInfoView.CoinNumber = "10";
VideoInfoView.DanmakuNumber = "1";
VideoInfoView.FavoriteNumber = "1";
VideoInfoView.LikeNumber = "10";
VideoInfoView.PlayNumber = "0";
VideoInfoView.ReplyNumber = "1";
VideoInfoView.ShareNumber = "1";
#endregion
}
#region
@ -24,6 +61,30 @@ public partial class VideoDetailViewModel : ViewModelBase
await NavigationService.BackwardAsync(parameter);
}
[RelayCommand]
private void Input() { }
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task DownloadManager()
{
await NavigationService.ForwardAsync(DownloadManagerViewModel.Key);
}
[RelayCommand]
private void CopyCover() { }
[RelayCommand]
private void CopyCoverUrl() { }
[RelayCommand]
private void Upper() { }
#endregion
public override void OnNavigatedTo(Dictionary<string, object>? parameter)
{
base.OnNavigatedTo(parameter);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AssemblyName>Downkyi</AssemblyName>
@ -12,7 +12,7 @@
<Company>DownKyi</Company>
<Product>DownKyi</Product>
<Description>DownKyi</Description>
<Copyright>Copyright © Downkyi 2020-2023</Copyright>
<Copyright>Copyright © Downkyi 2020-2024</Copyright>
<Version>2.0.0</Version>
<FileVersion>2.0.0</FileVersion>
<AssemblyVersion>2.0.0</AssemblyVersion>
@ -32,21 +32,21 @@
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="Avalonia" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
<PackageReference Include="Avalonia" Version="11.1.1" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.1" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.1" />
<PackageReference Include="Avalonia.Gif" Version="1.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.2" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.5.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Gif\Avalonia.Gif.csproj" />
<ProjectReference Include="..\Downkyi.UI\Downkyi.UI.csproj" />
</ItemGroup>

View File

@ -19,8 +19,8 @@
<!-- Index -->
<sys:String x:Key="Login">登录</sys:String>
<sys:String x:Key="IndexHintText">请输入B站网址链接或av、BV号等……</sys:String>
<sys:String x:Key="IndexHintTextSimple">请输入B站视频播放地址……</sys:String>
<sys:String x:Key="IndexHintText">请输入B站网址链接或av、BV号等......</sys:String>
<sys:String x:Key="IndexHintTextSimple">请输入B站视频播放地址......</sys:String>
<sys:String x:Key="Settings">设置</sys:String>
<sys:String x:Key="DownloadManager">下载管理</sys:String>
<sys:String x:Key="Toolbox">工具箱</sys:String>
@ -120,6 +120,7 @@
<sys:String x:Key="WhisperFollowing">悄悄关注</sys:String>
<!-- VideoDetail -->
<sys:String x:Key="HintText">请输入B站视频播放地址......</sys:String>
<sys:String x:Key="CopyCover">复制封面图片</sys:String>
<sys:String x:Key="CopyCoverUrl">复制封面URL</sys:String>
<sys:String x:Key="Play">播放</sys:String>

View File

@ -1,5 +1,4 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using Downkyi.Services;
using Downkyi.UI.Services;
using System;
using System.Text.RegularExpressions;

View File

@ -46,6 +46,12 @@ public static class ServiceLocator
public static UserSpaceViewModel UserSpaceViewModel =>
Ioc.Default.GetRequiredService<UserSpaceViewModel>();
// 视频页面
public static PublicFavoritesViewModel PublicFavoritesViewModel =>
Ioc.Default.GetRequiredService<PublicFavoritesViewModel>();
public static VideoDetailViewModel VideoDetailViewModel =>
Ioc.Default.GetRequiredService<VideoDetailViewModel>();
// 设置
public static SettingsViewModel SettingsViewModel =>
Ioc.Default.GetRequiredService<SettingsViewModel>();
@ -126,8 +132,8 @@ public static class ServiceLocator
.AddSingleton<MySpaceViewModel>()
.AddSingleton<UserSpaceViewModel>()
//
.AddSingleton<VideoDetailViewModel>()
.AddSingleton<PublicFavoritesViewModel>()
.AddSingleton<VideoDetailViewModel>()
.BuildServiceProvider());
}
}

View File

@ -2,6 +2,7 @@
<!-- TextBox.main -->
<Style Selector="TextBox.main">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="CornerRadius" Value="20" />
@ -19,6 +20,26 @@
<Setter Property="BorderBrush" Value="{DynamicResource BrushPrimary}" />
</Style>
<!-- TextBox.main2 -->
<Style Selector="TextBox.main2">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="CornerRadius" Value="16" />
<Setter Property="Height" Value="32" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Padding" Value="16,0,0,0" />
<Setter Property="SelectionBrush" Value="{DynamicResource BrushPrimaryTranslucent}" />
<Setter Property="SelectionForegroundBrush" Value="White" />
<Setter Property="BorderBrush" Value="{DynamicResource BrushPrimary}" />
</Style>
<Style Selector="TextBox.main2:pointerover /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource BrushPrimary}" />
</Style>
<Style Selector="TextBox.main2:focus /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource BrushPrimary}" />
</Style>
<!-- TextBox.normal -->
<Style Selector="TextBox.normal">
<Setter Property="Background" Value="Transparent" />

View File

@ -13,6 +13,7 @@ using Downkyi.UI.ViewModels.Login;
using Downkyi.UI.ViewModels.Settings;
using Downkyi.UI.ViewModels.Toolbox;
using Downkyi.UI.ViewModels.User;
using Downkyi.UI.ViewModels.Video;
using System;
using System.Collections.Generic;
using System.Threading;
@ -168,6 +169,12 @@ public partial class MainWindowViewModel : ViewModelBase
case ToolboxViewModel.Key:
viewModel = Ioc.Default.GetRequiredService<ToolboxViewModel>();
break;
case VideoDetailViewModel.Key:
viewModel = Ioc.Default.GetRequiredService<VideoDetailViewModel>();
break;
case PublicFavoritesViewModel.Key:
viewModel = Ioc.Default.GetRequiredService<PublicFavoritesViewModel>();
break;
default:
break;
}

View File

@ -139,7 +139,6 @@
<Grid Grid.Row="2" ColumnDefinitions="*,4*,*">
<TextBox
Grid.Column="1"
VerticalContentAlignment="Center"
Classes="main"
Text="{Binding Path=InputText, Mode=TwoWay}"
Watermark="{DynamicResource IndexHintText}">

View File

@ -4,11 +4,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="SplashScreen"
Title="{DynamicResource AppName}"
Width="500"
Height="300"
d:DesignHeight="300"
d:DesignWidth="500"
Background="Transparent"
Icon="/Assets/logo.ico"
SystemDecorations="None"
WindowStartupLocation="CenterScreen"

View File

@ -0,0 +1,385 @@
<UserControl
x:Class="Downkyi.Views.Video.VideoDetailView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:gif="clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Downkyi.UI.ViewModels.Video"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="vm:VideoDetailViewModel"
Design.DataContext="{x:Static local:ServiceLocator.VideoDetailViewModel}"
mc:Ignorable="d">
<UserControl.Resources>
<DrawingImage x:Key="DownloadManagerDrawingImage">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V20.01 H24.15 V0 H0 Z">
<DrawingGroup.Transform>
<TranslateTransform X="0.012936355546116829" Y="0.0082860654219985" />
</DrawingGroup.Transform>
<DrawingGroup Opacity="1">
<DrawingGroup Opacity="1">
<GeometryDrawing Brush="{DynamicResource BrushPrimary}" Geometry="F1 M24.15,20.01z M0,0z M11.9,19.91L4.6,19.91A4.38,4.38,0,0,1,0,15.21L0,4.71A4.38,4.38,0,0,1,4.6,0L8.5,0A3,3,0,0,1,11.2,1.3A4.45,4.45,0,0,0,15.8,3.4A17.83,17.83,0,0,1,19.8,3.4A4.25,4.25,0,0,1,24,7.71A57.48,57.48,0,0,1,24,16A4.2,4.2,0,0,1,19.7,20C16.9,19.9,14.4,19.9,11.9,19.9z M11.8,18.51L19.3,18.51A2.76,2.76,0,0,0,22.3,15.81A60.28,60.28,0,0,0,22.3,7.31A2.7,2.7,0,0,0,19.8,4.71A25.41,25.41,0,0,0,15.9,4.61C11.8,4.51,12.9,5.11,9.9,2.01A2.29,2.29,0,0,0,8.2,1.21L4.4,1.21A3.07,3.07,0,0,0,1.1,4.41L1.1,15.11A2.94,2.94,0,0,0,4.3,18.31C6.9,18.51,9.4,18.51,11.8,18.51z" />
<GeometryDrawing Brush="{DynamicResource BrushPrimary}" Geometry="F1 M24.15,20.01z M0,0z M17.8,0.11L21.1,0.11C21.6,0.11 22,0.11 22,0.81 22,1.51 21.6,1.61 21.1,1.61L14.4,1.61C13.9,1.61 13.5,1.51 13.5,0.91 13.5,0.31 13.9,0.21 14.4,0.21 15.5,0.11 16.6,0.11 17.8,0.11z M11.2,13.51L11.2,8.11C11.2,7.61 11.2,7.01 11.9,7.01 12.6,7.01 12.5,7.61 12.5,8.01L12.5,13.21 14.2,11.51C14.5,11.11 14.9,10.81 15.4,11.31 15.9,11.81 15.5,12.21 15.2,12.51 14.3,13.51 13.4,14.41 12.5,15.31 12.1,15.81 11.7,15.81 11.2,15.41 10.2,14.41 9.2,13.31 8.2,12.31A0.68,0.68,0,0,1,8.1,11.31C8.4,10.91,8.8,11.01,9.1,11.41A10.43,10.43,0,0,1,11.2,13.51z" />
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</UserControl.Resources>
<Grid RowDefinitions="50,10,*">
<Grid
Grid.Row="0"
Height="50"
ColumnDefinitions="40,*,50">
<Button
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="transparent"
Command="{Binding BackwardCommand}">
<Image
Width="24"
Height="24"
Source="{StaticResource ArrowBackDrawingImage}" />
</Button>
<TextBox
Grid.Column="1"
Classes="main2"
Text="{Binding Path=InputText, Mode=TwoWay}"
Watermark="{DynamicResource HintText}">
<TextBox.KeyBindings>
<KeyBinding Command="{Binding InputCommand}" Gesture="Enter" />
</TextBox.KeyBindings>
<TextBox.InnerRightContent>
<gif:GifImage
Width="20"
Height="20"
Margin="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding LoadingVisibility}"
SourceUri="avares://Downkyi/Assets/bili/loading/loading.gif"
Stretch="Uniform" />
</TextBox.InnerRightContent>
</TextBox>
<Button
Grid.Column="2"
Width="24"
Height="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="transparent"
Command="{Binding DownloadManagerCommand}"
Cursor="Hand">
<Image
Width="24"
Height="24"
Source="{StaticResource DownloadManagerDrawingImage}" />
</Button>
</Grid>
<Image
Grid.Row="1"
Opacity="0.3"
Source="/Assets/gradient.png"
Stretch="Fill" />
<Grid Grid.Row="2" IsVisible="{Binding ContentVisibility}">
<Grid.RowDefinitions>
<RowDefinition Height="100*" MaxHeight="180" />
<RowDefinition Height="200*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="10,0">
<Image
MinWidth="150"
MaxWidth="300"
VerticalAlignment="Top"
asyncImageLoader:ImageLoader.Source="{Binding VideoInfoView.Cover}"
DockPanel.Dock="Left">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding CopyCoverCommand}" Header="{DynamicResource CopyCover}" />
<MenuItem Command="{Binding CopyCoverUrlCommand}" Header="{DynamicResource CopyCoverUrl}" />
<!-- TODO 复制封面到文件 -->
</ContextMenu>
</Image.ContextMenu>
</Image>
<Grid
Margin="10,0,0,0"
VerticalAlignment="Stretch"
DockPanel.Dock="Left"
RowDefinitions="70,*">
<Grid Grid.Row="0" ColumnDefinitions="*,120">
<!-- 标题、分区、发布时间番剧不显示、播放量、弹幕数量、up主 -->
<Grid Grid.Column="0" RowDefinitions="3*,2*,2*">
<TextBlock
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Text="{Binding VideoInfoView.Title}"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding VideoInfoView.Title}" />
<StackPanel
Grid.Row="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.VideoZone}" />
<TextBlock Foreground="{DynamicResource BrushTextGrey}" Text="{Binding VideoInfoView.CreateTime}" />
</StackPanel>
<StackPanel
Grid.Row="2"
VerticalAlignment="Center"
Orientation="Horizontal">
<StackPanel
Name="PlayNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.PlayNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Play}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.PlayNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #PlayNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="DanmakuNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.DanmakuNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Danmaku}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.DanmakuNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #DanmakuNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="LikeNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.LikeNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Like}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.LikeNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #LikeNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="CoinNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.CoinNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Coin}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.CoinNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #CoinNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="FavoriteNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.FavoriteNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Favorite}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.FavoriteNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #FavoriteNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="ShareNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.ShareNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Share}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.ShareNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #ShareNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
<StackPanel
Name="ReplyNumber"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding VideoInfoView.ReplyNumber}" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextGrey}"
Text="{DynamicResource Reply}" />
<i:Interaction.Behaviors>
<ia:DataTriggerBehavior
Binding="{Binding VideoInfoView.ReplyNumber}"
ComparisonCondition="LessThanOrEqual"
Value="0">
<ia:ChangePropertyAction
PropertyName="IsVisible"
TargetObject="{Binding #ReplyNumber}"
Value="False" />
</ia:DataTriggerBehavior>
</i:Interaction.Behaviors>
</StackPanel>
</StackPanel>
</Grid>
<StackPanel
Name="nameUp"
Grid.Column="1"
Cursor="Hand"
ToolTip.Tip="{Binding VideoInfoView.UpName}">
<i:Interaction.Behaviors>
<ia:EventTriggerBehavior EventName="Tapped">
<ia:InvokeCommandAction Command="{Binding UpperCommand}" />
</ia:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image
Width="48"
Height="48"
asyncImageLoader:ImageLoader.Source="{Binding VideoInfoView.UpHeader}">
<Image.Clip>
<EllipseGeometry
Center="24,24"
RadiusX="24"
RadiusY="24" />
</Image.Clip>
</Image>
<TextBlock
Margin="0,2,0,0"
HorizontalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{Binding VideoInfoView.UpName}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</Grid>
<ScrollViewer
Grid.Row="1"
Margin="0,5,0,0"
VerticalScrollBarVisibility="Auto">
<TextBox
Background="{x:Null}"
BorderBrush="{x:Null}"
BorderThickness="0"
IsReadOnly="True"
Text="{Binding VideoInfoView.Description}"
TextWrapping="WrapWithOverflow" />
</ScrollViewer>
</Grid>
</DockPanel>
<Grid
Grid.Row="1"
Margin="10"
RowDefinitions="*,40" />
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Downkyi.Views.Video
{
public partial class VideoDetailView : UserControl
{
public VideoDetailView()
{
InitializeComponent();
}
}
}