diff --git a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameEntity.cs b/PCL.Neo.Core/Models/Minecraft/Game/Data/GameEntity.cs index fd4163ce..315602a0 100644 --- a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameEntity.cs +++ b/PCL.Neo.Core/Models/Minecraft/Game/Data/GameEntity.cs @@ -4,16 +4,20 @@ namespace PCL.Neo.Core.Models.Minecraft.Game.Data; public record GameEntityInfo { -#nullable disable /// /// The Game Version information. /// - public GameVersion GameVersion { get; set; } + public GameVersionNum? GameVersion { get; init; } + + /// + /// String typed game version. eg: 25w19a、1.21.5-rc2、25w14craftmine. + /// + public required string GameVersionString { get; init; } /// /// Game Name that is used to display in the UI. /// - public string Name { get; set; } + public required string Name { get; set; } /// /// Game Description that is used to display in the UI. @@ -23,12 +27,12 @@ public record GameEntityInfo /// /// Game Folder Path. /// - public string GamePath { get; set; } + public required string GamePath { get; set; } /// /// Game Root Path. /// - public string RootPath { get; set; } + public required string RootPath { get; set; } /// /// Game Icon that is used to display in the UI. @@ -38,21 +42,21 @@ public record GameEntityInfo /// /// The origin Game Json Content. Type is . /// - public string JsonOrigContent { get; set; } + public required string JsonOrigContent { get; set; } /// /// The Parsed Game Json Content. Type is . /// - public MetadataFile JsonContent { get; set; } + public required MetadataFile JsonContent { get; set; } /// /// Demonstrate the Game Version Type. - /// Content is . + /// Content is . /// - public VersionType Type { get; set; } + public VersionCardType Type { get; set; } /// - /// If is .Modable, Loader will have value that is used to display in the UI. + /// If is .Moddable, Loader will have value that is used to display in the UI. /// public ModLoader Loader { get; set; } @@ -63,9 +67,9 @@ public record GameEntityInfo /// /// Demonstrate is the version has been loader (runed). /// - public bool IsLoadded { get; set; } = false; + public bool IsLoaded { get; set; } = false; - private bool? _isIndie { get; set; } + private bool? _isIndie; public bool IsIndie { @@ -86,8 +90,7 @@ public bool IsIndie /// /// THe Game Jar File Path. /// - public string JarPath { get; set; } -#nullable enable + public required string JarPath { get; set; } } public class GameEntity diff --git a/PCL.Neo.Core/Models/Minecraft/Game/Data/VersionInfo.cs b/PCL.Neo.Core/Models/Minecraft/Game/Data/VersionInfo.cs index aac376ac..8ecbe01b 100644 --- a/PCL.Neo.Core/Models/Minecraft/Game/Data/VersionInfo.cs +++ b/PCL.Neo.Core/Models/Minecraft/Game/Data/VersionInfo.cs @@ -17,21 +17,34 @@ public enum Icons : byte NeoForge = 12 } - public enum VersionType : byte + public enum VersionCardType : byte { Auto = 0, Hide = 1, - Modable = 2, + Moddable = 2, Normal = 3, Unusual = 4, - FoolDay = 5 + FoolsDay = 5, + Error = 6, } - public record GameVersion + public record GameVersionNum(byte Sub, byte? Fix = null) : IComparable { - public byte Major { get; set; } = 1; - public byte Sub { get; set; } - public byte Fix { get; set; } + private readonly (byte Major, byte Sub, int Fix) _version = (1, Sub, Fix ?? 0); + + public byte Major => _version.Major; + public byte Sub => _version.Sub; + public byte? Fix => _version.Fix > 0 ? (byte)_version.Fix : null; + + public int CompareTo(GameVersionNum? other) + { + return other == null ? 1 : (Major, Sub, Fix ?? 0).CompareTo((other.Major, other.Sub, other.Fix ?? 0)); + } + + public override string ToString() + { + return Fix.HasValue ? $"{Major}.{Sub}.{Fix}" : $"{Major}.{Sub}"; + } } public enum ModLoader : byte @@ -44,4 +57,18 @@ public enum ModLoader : byte Rift = 5, Quilt = 6 } + + public enum McVersionState + { + Error, + Vanilla, + Snapshot, + FoolsDay, + OptiFine, + Legacy, + Forge, + NeoForge, + LiteLoader, + Fabric, + } } diff --git a/PCL.Neo.Core/Models/Minecraft/Java/IJavaManager.cs b/PCL.Neo.Core/Models/Minecraft/Java/IJavaManager.cs index 11eed3ae..58a23cd6 100644 --- a/PCL.Neo.Core/Models/Minecraft/Java/IJavaManager.cs +++ b/PCL.Neo.Core/Models/Minecraft/Java/IJavaManager.cs @@ -4,6 +4,6 @@ public interface IJavaManager { List JavaList { get; } Task JavaListInit(); - Task ManualAdd(string javaDir); + Task<(JavaRuntime?, bool UpdateCurrent)> ManualAdd(string javaDir); Task Refresh(); } diff --git a/PCL.Neo.Core/Models/Minecraft/Java/JavaManager.cs b/PCL.Neo.Core/Models/Minecraft/Java/JavaManager.cs index 317a1f31..d125bd19 100644 --- a/PCL.Neo.Core/Models/Minecraft/Java/JavaManager.cs +++ b/PCL.Neo.Core/Models/Minecraft/Java/JavaManager.cs @@ -56,14 +56,14 @@ public async Task JavaListInit() } } - public async Task ManualAdd(string javaDir) + public async Task<(JavaRuntime?, bool UpdateCurrent)> ManualAdd(string javaDir) { - if (!IsInitialized) return; + if (!IsInitialized) return (null, false); if (JavaList.FirstOrDefault(runtime => runtime.DirectoryPath == javaDir) is { } existingRuntime) { Console.WriteLine("选择的 Java 在列表中已存在,将其标记为手动导入。"); existingRuntime.IsUserImport = true; - return; + return (existingRuntime, true); } var entity = await JavaRuntime.CreateJavaEntityAsync(javaDir, true); @@ -71,8 +71,10 @@ public async Task ManualAdd(string javaDir) { JavaList.Add(entity); Console.WriteLine("已成功添加!"); + return (entity, false); } - else Console.WriteLine("添加的 Java 文件无法运行!"); + Console.WriteLine("添加的 Java 文件无法运行!"); + return (null, false); } public async Task Refresh() @@ -93,7 +95,7 @@ public async Task Refresh() else Console.WriteLine($"[Java] 用户导入的 Java 已不可用,已自动剔除:{oldRuntime.DirectoryPath}"); JavaList = newEntities; - Console.WriteLine("[Java] 刷新 Java 完成"); + Console.WriteLine($"[Java] 刷新 Java 完成,现在共有 {JavaList.Count} 个Java"); if (JavaList.Count == 0) { // TODO)) 提示用户未找到已安装的 java,是否自动下载合适版本,然后再下载 diff --git a/PCL.Neo.Core/PCL.Neo.Core.csproj b/PCL.Neo.Core/PCL.Neo.Core.csproj index a99d4376..2e2b8240 100644 --- a/PCL.Neo.Core/PCL.Neo.Core.csproj +++ b/PCL.Neo.Core/PCL.Neo.Core.csproj @@ -7,7 +7,7 @@ - + diff --git a/PCL.Neo/App.axaml.cs b/PCL.Neo/App.axaml.cs index fc5add67..a164d431 100644 --- a/PCL.Neo/App.axaml.cs +++ b/PCL.Neo/App.axaml.cs @@ -1,21 +1,19 @@ using System.Linq; using Avalonia; -using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; using CommunityToolkit.Mvvm.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using PCL.Neo.Services; -using Avalonia.Platform.Storage; -using PCL.Neo.Helpers; -using PCL.Neo.Utils; +using PCL.Neo.Core.Models.Minecraft.Java; using PCL.Neo.ViewModels; using PCL.Neo.ViewModels.Download; using PCL.Neo.ViewModels.Home; using PCL.Neo.Views; using System; using System.Threading.Tasks; +using PCL.Neo.ViewModels.Setup; namespace PCL.Neo { @@ -39,8 +37,12 @@ public override void Initialize() .AddTransient() .AddTransient() + .AddTransient() + .AddTransient() + .AddSingleton(s => new NavigationService(s)) .AddSingleton() + .AddSingleton() .BuildServiceProvider(); public override void OnFrameworkInitializationCompleted() @@ -48,6 +50,7 @@ public override void OnFrameworkInitializationCompleted() Ioc.Default.ConfigureServices(ConfigureServices()); var vm = Ioc.Default.GetRequiredService(); + Task.Run(Ioc.Default.GetRequiredService().JavaListInit); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { // Avoid duplicate validations from both Avalonia and the CommunityToolkit. diff --git a/PCL.Neo/PCL.Neo.csproj b/PCL.Neo/PCL.Neo.csproj index 962f4588..1c5bf909 100644 --- a/PCL.Neo/PCL.Neo.csproj +++ b/PCL.Neo/PCL.Neo.csproj @@ -38,7 +38,6 @@ - @@ -68,10 +67,11 @@ - - - + + + + diff --git a/PCL.Neo/ViewModels/MainWindowViewModel.cs b/PCL.Neo/ViewModels/MainWindowViewModel.cs index 0bb8ad43..2b8a5fd4 100644 --- a/PCL.Neo/ViewModels/MainWindowViewModel.cs +++ b/PCL.Neo/ViewModels/MainWindowViewModel.cs @@ -70,6 +70,12 @@ public void NavigateToDownload() this.NavigationService.Goto(); } + [RelayCommand] + public void NavigateToSetup() + { + this.NavigationService.Goto(); + } + public void Close() { _window?.Close(); diff --git a/PCL.Neo/ViewModels/Setup/SetupLaunchViewModel.cs b/PCL.Neo/ViewModels/Setup/SetupLaunchViewModel.cs new file mode 100644 index 00000000..cbb2eb07 --- /dev/null +++ b/PCL.Neo/ViewModels/Setup/SetupLaunchViewModel.cs @@ -0,0 +1,59 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PCL.Neo.Core.Models.Minecraft.Java; +using PCL.Neo.Services; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; + +namespace PCL.Neo.ViewModels.Setup; + +public record JavaUiInfo(JavaRuntime Runtime) +{ + public string Identifier => + $"{(Runtime.IsJre ? "JRE" : "JDK")} {Runtime.SlugVersion} ({Runtime.Version}) {Runtime.Architecture} {Runtime.Implementor}"; + + public string Path => Runtime.DirectoryPath; +} + +[SubViewModelOf(typeof(SetupViewModel))] +public partial class SetupLaunchViewModel : ViewModelBase +{ + private readonly IJavaManager _javaManager; + private readonly StorageService _storageService; + [ObservableProperty] private ObservableCollection _javaInfoList = []; + + private void DoUiRefresh() + { + if (JavaInfoList.Count != 0) JavaInfoList.Clear(); + foreach (JavaRuntime runtime in _javaManager.JavaList) + JavaInfoList.Add(new JavaUiInfo(runtime)); + } + + public SetupLaunchViewModel(IJavaManager javaManager, StorageService storageService) + { + _javaManager = javaManager; + _storageService = storageService; + DoUiRefresh(); + } + + [RelayCommand] + private async Task RefreshJava() + { + JavaInfoList.Clear(); + await _javaManager.Refresh(); + DoUiRefresh(); + } + + [RelayCommand] + private async Task ManualAdd() + { + string? javaPath = await _storageService.SelectFile("选择要添加的Java"); + if (javaPath == null) return; + var dirPath = Path.GetDirectoryName(javaPath); + if (dirPath == null) return; + (JavaRuntime? resultRuntime, bool updateCurrent) = await _javaManager.ManualAdd(dirPath); + if (resultRuntime == null || updateCurrent) return; + JavaInfoList.Add(new JavaUiInfo(resultRuntime)); + } +} \ No newline at end of file diff --git a/PCL.Neo/ViewModels/SetupViewModel.cs b/PCL.Neo/ViewModels/SetupViewModel.cs new file mode 100644 index 00000000..0f71d8ac --- /dev/null +++ b/PCL.Neo/ViewModels/SetupViewModel.cs @@ -0,0 +1,9 @@ +using PCL.Neo.ViewModels.Setup; + +namespace PCL.Neo.ViewModels; + +[DefaultSubViewModel(typeof(SetupLaunchViewModel))] +public class SetupViewModel : ViewModelBase +{ + +} \ No newline at end of file diff --git a/PCL.Neo/Views/MainWindow.axaml b/PCL.Neo/Views/MainWindow.axaml index cc47233e..3cb747e9 100644 --- a/PCL.Neo/Views/MainWindow.axaml +++ b/PCL.Neo/Views/MainWindow.axaml @@ -182,7 +182,7 @@ Name="BtnTitleSelect2" Padding="2,0" Tag="2" - Text="{DynamicResource LangTitleLink}" /> + Text="{DynamicResource LangTitleLink}"/> + Text="{DynamicResource LangTitleSetup}" + Command="{Binding NavigateToSetup}"/> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PCL.Neo/Views/Setup/SetupLaunchView.axaml.cs b/PCL.Neo/Views/Setup/SetupLaunchView.axaml.cs new file mode 100644 index 00000000..7ffd1b6a --- /dev/null +++ b/PCL.Neo/Views/Setup/SetupLaunchView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace PCL.Neo.Views.Setup +{ + public partial class SetupLaunchView : UserControl + { + public SetupLaunchView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/PCL.Neo/Views/Setup/SetupOtherView.axaml b/PCL.Neo/Views/Setup/SetupOtherView.axaml new file mode 100644 index 00000000..25336113 --- /dev/null +++ b/PCL.Neo/Views/Setup/SetupOtherView.axaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + + diff --git a/PCL.Neo/Views/Setup/SetupOtherView.axaml.cs b/PCL.Neo/Views/Setup/SetupOtherView.axaml.cs new file mode 100644 index 00000000..4249e3d5 --- /dev/null +++ b/PCL.Neo/Views/Setup/SetupOtherView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace PCL.Neo.Views.Setup +{ + public partial class SetupOtherView : UserControl + { + public SetupOtherView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/PCL.Neo/Views/Setup/SetupStyleView.axaml b/PCL.Neo/Views/Setup/SetupStyleView.axaml new file mode 100644 index 00000000..bd2ad1af --- /dev/null +++ b/PCL.Neo/Views/Setup/SetupStyleView.axaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + + diff --git a/PCL.Neo/Views/Setup/SetupStyleView.axaml.cs b/PCL.Neo/Views/Setup/SetupStyleView.axaml.cs new file mode 100644 index 00000000..8803f67e --- /dev/null +++ b/PCL.Neo/Views/Setup/SetupStyleView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace PCL.Neo.Views.Setup +{ + public partial class SetupStyleView : UserControl + { + public SetupStyleView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/PCL.Neo/Views/SetupView.axaml b/PCL.Neo/Views/SetupView.axaml new file mode 100644 index 00000000..fba3fb7a --- /dev/null +++ b/PCL.Neo/Views/SetupView.axaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/PCL.Neo/Views/SetupView.axaml.cs b/PCL.Neo/Views/SetupView.axaml.cs new file mode 100644 index 00000000..90351bc5 --- /dev/null +++ b/PCL.Neo/Views/SetupView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace PCL.Neo.Views +{ + public partial class SetupView : UserControl + { + public SetupView() + { + InitializeComponent(); + } + } +} \ No newline at end of file