сырой проект

This commit is contained in:
2026-09-14 20:24:13 +02:00
commit 81b806cfed
14 changed files with 953 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SpotXInstaller.App"
xmlns:local="using:SpotXInstaller"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator />
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
+28
View File
@@ -0,0 +1,28 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using SpotXInstaller.ViewModels;
using SpotXInstaller.Views;
namespace SpotXInstaller;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+7
View File
@@ -0,0 +1,7 @@
namespace SpotXInstaller.Models;
public sealed record InstallOptions(
bool BlockUpdates,
bool HidePodcasts,
bool HidePremium,
bool ClearCache);
+24
View File
@@ -0,0 +1,24 @@
using Avalonia;
using System;
namespace SpotXInstaller;
sealed class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
#if DEBUG
.WithDeveloperTools()
#endif
.WithInterFont()
.LogToTrace();
}
+159
View File
@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using SpotXInstaller.Models;
namespace SpotXInstaller.Services;
public sealed class InstallerService
{
private const string BashUrl = "https://api.kirieska.xyz/files/SPOTX/spotx.sh";
private const string PsUrl = "https://api.kirieska.xyz/files/SPOTX/run.ps1";
private const string PsUninstallUrl = "https://api.kirieska.xyz/files/SPOTX/uninstall.ps1";
public Task<int> InstallAsync(InstallOptions o,
IProgress<(int Percent, string Status)> progress,
IProgress<string> log, CancellationToken ct = default)
=> RunAsync(o, isUninstall: false, progress, log, ct);
public Task<int> UninstallAsync(
IProgress<(int Percent, string Status)> progress,
IProgress<string> log, CancellationToken ct = default)
=> RunAsync(new InstallOptions(false, false, false, false),
isUninstall: true, progress, log, ct);
private async Task<int> RunAsync(InstallOptions o, bool isUninstall,
IProgress<(int, string)> progress, IProgress<string> log, CancellationToken ct)
{
bool isWin = OperatingSystem.IsWindows();
string url = isUninstall
? (isWin ? PsUninstallUrl : BashUrl)
: (isWin ? PsUrl : BashUrl);
string ext = isWin ? ".ps1" : ".sh";
string scriptPath = Path.Combine(Path.GetTempPath(), $"spotx_{Guid.NewGuid():N}{ext}");
try
{
// === ШАГ 1 (0–20%): скачивание ===
progress.Report((3, "Загрузка скрипта..."));
using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) })
{
http.DefaultRequestHeaders.UserAgent.ParseAdd("SpotXInstaller/1.0");
byte[] data = await http.GetByteArrayAsync(url, ct).ConfigureAwait(false);
await File.WriteAllBytesAsync(scriptPath, data, ct).ConfigureAwait(false);
}
progress.Report((20, "Скрипт загружен"));
if (!isWin)
{
progress.Report((25, "Установка прав на исполнение..."));
await ExecAsync("/bin/chmod", new[] { "+x", scriptPath }, _ => { }, ct)
.ConfigureAwait(false);
}
progress.Report((30, isUninstall ? "Восстановление..." : "Запуск установки..."));
// === ШАГ 2: маппинг флагов ===
string file;
var argv = new List<string>();
if (isWin)
{
file = "powershell.exe";
argv.AddRange(new[]
{
"-NoProfile", "-ExecutionPolicy", "Bypass",
"-File", scriptPath
});
if (isUninstall)
{
// uninstall.ps1 не принимает флагов — просто отрабатывает
}
else
{
if (o.BlockUpdates) argv.Add("-block_update_on");
if (o.HidePodcasts) argv.Add("-podcasts_off");
if (o.HidePremium) argv.Add("-adsections_off");
argv.Add("-no_pause"); // не ждать Enter в конце
}
}
else
{
file = "/bin/bash";
argv.Add(scriptPath);
if (isUninstall)
{
argv.Add("--uninstall");
}
else
{
if (o.BlockUpdates) argv.Add("-B");
if (o.HidePodcasts) argv.Add("-h");
if (o.HidePremium) argv.Add("-e");
if (o.ClearCache) argv.Add("-c");
}
}
// === ШАГ 3 (30–95%): запуск + построчный стрим ===
int cur = 30;
var gate = new object();
int exit = await ExecAsync(file, argv.ToArray(), line =>
{
log.Report(line);
lock (gate)
{
cur = Math.Min(cur + 2, 95);
progress.Report((cur, isUninstall ? "Восстановление..." : "Установка..."));
}
}, ct).ConfigureAwait(false);
progress.Report((100, "Готово"));
return exit;
}
finally
{
try { if (File.Exists(scriptPath)) File.Delete(scriptPath); } catch { }
}
}
private static Task<int> ExecAsync(string file, string[] argv,
Action<string> onLine, CancellationToken ct)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
var psi = new ProcessStartInfo
{
FileName = file,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
foreach (var a in argv) psi.ArgumentList.Add(a);
var proc = new Process { StartInfo = psi, EnableRaisingEvents = true };
proc.OutputDataReceived += (_, e) => { if (e.Data is { } s) onLine(s); };
proc.ErrorDataReceived += (_, e) => { if (e.Data is { } s) onLine(s); };
proc.Exited += (_, _) =>
{
try { tcs.TrySetResult(proc.ExitCode); }
finally { proc.Dispose(); }
};
using var reg = ct.Register(() =>
{
try { if (!proc.HasExited) proc.Kill(entireProcessTree: true); }
catch { }
});
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
return tcs.Task;
}
}
+25
View File
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.2" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.2" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
</ItemGroup>
</Project>
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using SpotXInstaller.ViewModels;
namespace SpotXInstaller;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}
+129
View File
@@ -0,0 +1,129 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SpotXInstaller.Models;
using SpotXInstaller.Services;
namespace SpotXInstaller.ViewModels;
public partial class MainViewModel : ViewModelBase
{
private readonly InstallerService _installer = new();
private CancellationTokenSource? _cts;
// --- СОСТОЯНИЯ ЭКРАНОВ ---
[ObservableProperty] private bool _isStepLoading = true;
[ObservableProperty] private bool _isStepHome;
[ObservableProperty] private bool _isStepSettings;
[ObservableProperty] private bool _isStepProgress;
// --- POPUP ---
[ObservableProperty] private bool _showPopup;
[ObservableProperty] private string _popupTitle = "";
[ObservableProperty] private string _popupMessage = "";
[ObservableProperty] private string _popupColor = "#8B5CF6";
// --- ГАЛОЧКИ ---
[ObservableProperty] private bool _blockUpdates = true;
[ObservableProperty] private bool _hidePodcasts = true;
[ObservableProperty] private bool _hidePremium = true;
[ObservableProperty] private bool _clearCache;
// --- ПРОГРЕСС ---
[ObservableProperty] private double _progressValue;
[ObservableProperty] private string _progressStatus = "Подготовка...";
[ObservableProperty] private string _miniLog = "";
public MainViewModel() => _ = BootAsync();
private async Task BootAsync()
{
await Task.Delay(3000);
SwitchTo(home: true);
}
private void SwitchTo(bool loading = false, bool home = false, bool settings = false, bool progress = false)
{
IsStepLoading = loading;
IsStepHome = home;
IsStepSettings = settings;
IsStepProgress = progress;
}
[RelayCommand] private void GoToSettings() => SwitchTo(settings: true);
[RelayCommand] private void GoBackToHome() => SwitchTo(home: true);
[RelayCommand] private void ClosePopup() => ShowPopup = false;
[RelayCommand]
private async Task StartInstallAsync()
{
await RunInstallerAsync(isUninstall: false);
}
[RelayCommand]
private async Task StartUninstallAsync()
{
await RunInstallerAsync(isUninstall: true);
}
private async Task RunInstallerAsync(bool isUninstall)
{
ProgressValue = 0;
MiniLog = "";
ProgressStatus = isUninstall ? "Подготовка восстановления..." : "Подготовка установки...";
SwitchTo(progress: true);
_cts?.Dispose();
_cts = new CancellationTokenSource();
var progress = new Progress<(int Percent, string Status)>(p =>
{
ProgressValue = p.Percent;
ProgressStatus = p.Status;
});
var log = new Progress<string>(l => MiniLog = l);
try
{
int code = isUninstall
? await _installer.UninstallAsync(progress, log, _cts.Token)
: await _installer.InstallAsync(
new InstallOptions(BlockUpdates, HidePodcasts, HidePremium, ClearCache),
progress, log, _cts.Token);
SwitchTo(home: true);
if (code == 0)
ShowNotification(
isUninstall ? "ГОТОВО" : "УСПЕХ",
isUninstall
? "Оригинальный клиент Spotify восстановлен."
: "SpotX успешно установлен.",
isUninstall ? "#8B5CF6" : "#00E676");
else
ShowNotification("ОШИБКА",
$"Скрипт завершился с кодом {code}.\n\n{MiniLog[..Math.Min(200, MiniLog.Length)]}",
"#FF3B30");
}
catch (OperationCanceledException)
{
SwitchTo(home: true);
ShowNotification("ОТМЕНЕНО", "Операция прервана.", "#FFB300");
}
catch (Exception ex)
{
SwitchTo(home: true);
ShowNotification("ОШИБКА", ex.Message, "#FF3B30");
}
}
private void ShowNotification(string title, string message, string hexColor)
{
PopupTitle = title;
PopupMessage = message;
PopupColor = hexColor;
ShowPopup = true;
}
}
+7
View File
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace SpotXInstaller.ViewModels;
public abstract class ViewModelBase : ObservableObject
{
}
+312
View File
@@ -0,0 +1,312 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:SpotXInstaller.ViewModels"
x:Class="SpotXInstaller.Views.MainWindow"
x:DataType="vm:MainViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="SpotX Installer"
Width="700" Height="500"
CanResize="False"
Background="#050505"
Foreground="White"
WindowStartupLocation="CenterScreen">
<Design.DataContext><vm:MainViewModel /></Design.DataContext>
<Window.Styles>
<!-- === ACCENT BUTTON === -->
<Style Selector="Button.accent">
<Setter Property="Background" Value="#8B5CF6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Padding" Value="24,16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#A78BFA"/>
</Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#7C3AED"/>
</Style>
<!-- === GHOST BUTTON === -->
<Style Selector="Button.ghost">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#B8B8B8"/>
<Setter Property="BorderBrush" Value="#26262B"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Padding" Value="20,14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="Button.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#101014"/>
<Setter Property="BorderBrush" Value="#8B5CF6"/>
</Style>
<!-- === WHITE BUTTON === -->
<Style Selector="Button.white">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Padding" Value="34,16"/>
<Setter Property="FontWeight" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="Button.white:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#E6E6E6"/>
</Style>
<!-- === BACK BUTTON === -->
<Style Selector="Button.back">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#8A8A8A"/>
<Setter Property="Padding" Value="10,6"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="Button.back:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="#101014"/>
</Style>
<!-- === PREMIUM CHECKBOX === -->
<Style Selector="CheckBox.accent">
<Setter Property="Foreground" Value="#DDDDDD"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="Auto,*" Background="Transparent">
<Border Name="Box" Width="20" Height="20" CornerRadius="6"
BorderBrush="#2A2A2E" BorderThickness="1.5" Background="#0D0D10">
<Path Name="Check"
Data="M 5,10 L 9,14 L 15,6"
Stroke="White" StrokeThickness="2"
StrokeLineCap="Round" StrokeJoin="Round"
IsVisible="False"/>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1" Margin="14,0,0,0"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CheckBox.accent:checked /template/ Border#Box">
<Setter Property="Background" Value="#8B5CF6"/>
<Setter Property="BorderBrush" Value="#8B5CF6"/>
</Style>
<Style Selector="CheckBox.accent:checked /template/ Path#Check">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CheckBox.accent:pointerover /template/ Border#Box">
<Setter Property="BorderBrush" Value="#8B5CF6"/>
</Style>
<!-- === PROGRESSBAR === -->
<Style Selector="ProgressBar.accent">
<Setter Property="Height" Value="6"/>
<Setter Property="CornerRadius" Value="3"/>
<Setter Property="Background" Value="#141418"/>
<Setter Property="Foreground" Value="#8B5CF6"/>
</Style>
</Window.Styles>
<Grid>
<!-- ================================================= -->
<!-- 1. SPLASH -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepLoading}" Background="#050507" ZIndex="10">
<Grid Width="500" Height="260" HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Width="390" Height="110" CornerRadius="55" Background="#8B5CF6" Opacity="0.08"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame Cue="0%"><Setter Property="Opacity" Value="0.05"/></KeyFrame>
<KeyFrame Cue="50%"><Setter Property="Opacity" Value="0.15"/></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Opacity" Value="0.05"/></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="KIRIESKA" FontSize="60" FontWeight="Black" Foreground="#18181D"/>
<Border HorizontalAlignment="Left" ClipToBounds="True">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2.5" FillMode="Forward">
<KeyFrame Cue="0%"><Setter Property="Width" Value="0"/></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Width" Value="400"/></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
<TextBlock Text="KIRIESKA" FontSize="60" FontWeight="Black" Foreground="#A78BFA"/>
</Border>
</Grid>
<TextBlock Text="INITIALIZING SYSTEM" FontSize="10" FontWeight="SemiBold"
Foreground="#666670" LetterSpacing="3"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,48"/>
<Border Width="280" Height="2" Background="#18181D" CornerRadius="1"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,30">
<Border Height="2" Background="#8B5CF6" CornerRadius="1" HorizontalAlignment="Left">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2.5" FillMode="Forward">
<KeyFrame Cue="0%"><Setter Property="Width" Value="0"/></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Width" Value="280"/></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
</Border>
<Ellipse Width="5" Height="5" Fill="#8B5CF6" HorizontalAlignment="Center"
VerticalAlignment="Bottom" Margin="0,0,0,18">
<Ellipse.Styles>
<Style Selector="Ellipse">
<Style.Animations>
<Animation Duration="0:0:1" IterationCount="Infinite">
<KeyFrame Cue="0%"><Setter Property="Opacity" Value="0.2"/></KeyFrame>
<KeyFrame Cue="50%"><Setter Property="Opacity" Value="1"/></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Opacity" Value="0.2"/></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Ellipse.Styles>
</Ellipse>
</Grid>
</Grid>
<!-- ================================================= -->
<!-- 2. HOME -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepHome}" Margin="40">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="340" Spacing="14">
<TextBlock Text="SpotX Installer" FontSize="38" FontWeight="Black"
HorizontalAlignment="Center" LetterSpacing="-0.5"/>
<TextBlock Text="Продвинутый патч для десктопного Spotify."
FontSize="14" Foreground="#666666"
HorizontalAlignment="Center" Margin="0,0,0,36"/>
<Button Content="НАЧАТЬ УСТАНОВКУ"
Command="{Binding GoToSettingsCommand}"
Classes="accent"
HorizontalAlignment="Stretch"/>
<Button Content="УДАЛИТЬ / ВОССТАНОВИТЬ"
Command="{Binding StartUninstallCommand}"
Classes="ghost"
HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
<!-- ================================================= -->
<!-- 3. SETTINGS -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepSettings}" Margin="40">
<Button Content="← Назад" Command="{Binding GoBackToHomeCommand}"
Classes="back"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
<StackPanel Margin="0,70,0,0" Spacing="4">
<TextBlock Text="Параметры" FontSize="30" FontWeight="Bold"/>
<TextBlock Text="Выберите, что изменить в Spotify"
FontSize="13" Foreground="#666666" Margin="0,4,0,0"/>
</StackPanel>
<StackPanel Spacing="18" Margin="0,150,0,0">
<CheckBox Classes="accent" Content="Блокировать автоматические обновления Spotify"
IsChecked="{Binding BlockUpdates}"/>
<CheckBox Classes="accent" Content="Скрыть раздел с подкастами"
IsChecked="{Binding HidePodcasts}"/>
<CheckBox Classes="accent" Content="Удалить кнопки покупки Premium"
IsChecked="{Binding HidePremium}"/>
<CheckBox Classes="accent" Content="Очистить кэш перед установкой"
IsChecked="{Binding ClearCache}"/>
</StackPanel>
<Button Content="УСТАНОВИТЬ" Command="{Binding StartInstallCommand}"
Classes="white"
HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
<!-- ================================================= -->
<!-- 4. PROGRESS -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepProgress}" Background="#050505" ZIndex="5">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
Width="480" Spacing="26">
<TextBlock Text="УСТАНОВКА" FontSize="11" FontWeight="Bold"
Foreground="#8B5CF6" LetterSpacing="4"
HorizontalAlignment="Center"/>
<TextBlock Text="{Binding ProgressStatus}" FontSize="22" FontWeight="Bold"
HorizontalAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap"/>
<ProgressBar Value="{Binding ProgressValue}"
Minimum="0" Maximum="100"
Classes="accent" Width="480"/>
<TextBlock Text="{Binding ProgressValue, StringFormat='{}{0:F0}%'}"
FontSize="12" Foreground="#666666"
HorizontalAlignment="Center"/>
<Border Background="#0A0A0C" BorderBrush="#141418" BorderThickness="1"
CornerRadius="10" Padding="16,12" Width="480" MinHeight="42">
<TextBlock Text="{Binding MiniLog}"
FontFamily="Consolas,Menlo,DejaVu Sans Mono,monospace"
FontSize="11" Foreground="#888888"
TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"/>
</Border>
</StackPanel>
</Grid>
<!-- ================================================= -->
<!-- 5. POPUP -->
<!-- ================================================= -->
<Grid IsVisible="{Binding ShowPopup}" Background="#D9000000" ZIndex="20">
<Border Width="420" Padding="32" Background="#101014"
BorderBrush="#22222A" BorderThickness="1"
CornerRadius="16"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Spacing="14">
<TextBlock Text="{Binding PopupTitle}"
Foreground="{Binding PopupColor}"
FontSize="18" FontWeight="Black" LetterSpacing="1"/>
<TextBlock Text="{Binding PopupMessage}"
Foreground="#CCCCCC" FontSize="14"
TextWrapping="Wrap" LineHeight="20"/>
<Button Content="ОК" Command="{Binding ClosePopupCommand}"
Classes="accent" Padding="28,10" FontSize="13"
HorizontalAlignment="Right" Margin="0,8,0,0"/>
</StackPanel>
</Border>
</Grid>
</Grid>
</Window>
+11
View File
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace SpotXInstaller.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
+181
View File
@@ -0,0 +1,181 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:SpotXInstaller.ViewModels"
x:Class="SpotXInstaller.Views.MainWindow"
x:DataType="vm:MainViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="SpotX Installer"
Width="700"
Height="500"
CanResize="False"
Background="#050505"
Foreground="White"
WindowStartupLocation="CenterScreen">
<Design.DataContext>
<vm:MainViewModel />
</Design.DataContext>
<Grid>
<!-- ================================================= -->
<!-- 1. LOADING SCREEN -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepLoading}" Background="#050507" ZIndex="10">
<Grid Width="500" Height="260" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Glow (Свечение на фоне) -->
<Border Width="390" Height="110" CornerRadius="55" Background="#8B5CF6" Opacity="0.08"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame Cue="0%"><Setter Property="Opacity" Value="0.05" /></KeyFrame>
<KeyFrame Cue="50%"><Setter Property="Opacity" Value="0.15" /></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Opacity" Value="0.05" /></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
<!-- ========================================================== -->
<!-- ИДЕАЛЬНОЕ НАЛОЖЕНИЕ ТЕКСТА (БЕЗ МАРДЖИНОВ И СМЕЩЕНИЙ) -->
<!-- ========================================================== -->
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Background text (Темный) -->
<TextBlock Text="KIRIESKA" FontSize="60" FontWeight="Black" Foreground="#18181D" />
<!-- Animated text (Фиолетовый) -->
<!-- Он лежит в том же Grid, поэтому координаты букв совпадут на 100% -->
<Border HorizontalAlignment="Left" ClipToBounds="True">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2.5" FillMode="Forward">
<KeyFrame Cue="0%"><Setter Property="Width" Value="0" /></KeyFrame>
<!-- Ставлю 400, чтобы ширины анимации точно хватило на всё слово на любом шрифте -->
<KeyFrame Cue="100%"><Setter Property="Width" Value="400" /></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
<TextBlock Text="KIRIESKA" FontSize="60" FontWeight="Black" Foreground="#A78BFA" />
</Border>
</Grid>
<!-- Status -->
<TextBlock Text="INITIALIZING SYSTEM" FontSize="10" FontWeight="SemiBold" Foreground="#666670"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,48" />
<!-- Progress background -->
<Border Width="280" Height="2" Background="#18181D" CornerRadius="1"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,30">
<!-- Progress -->
<Border Height="2" Background="#8B5CF6" CornerRadius="1" HorizontalAlignment="Left">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2.5" FillMode="Forward">
<KeyFrame Cue="0%"><Setter Property="Width" Value="0" /></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Width" Value="280" /></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>
</Border>
<!-- Loading dot -->
<Ellipse Width="5" Height="5" Fill="#8B5CF6" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,18">
<Ellipse.Styles>
<Style Selector="Ellipse">
<Style.Animations>
<Animation Duration="0:0:1" IterationCount="Infinite">
<KeyFrame Cue="0%"><Setter Property="Opacity" Value="0.2" /></KeyFrame>
<KeyFrame Cue="50%"><Setter Property="Opacity" Value="1" /></KeyFrame>
<KeyFrame Cue="100%"><Setter Property="Opacity" Value="0.2" /></KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Ellipse.Styles>
</Ellipse>
</Grid>
</Grid>
<!-- ================================================= -->
<!-- 2. HOME -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepHome}" Margin="40">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="320" Spacing="12">
<TextBlock Text="SpotX Installer" FontSize="36" FontWeight="Black" HorizontalAlignment="Center" />
<TextBlock Text="Продвинутый патч для десктопного Spotify." FontSize="14" Foreground="#666666"
HorizontalAlignment="Center" Margin="0,0,0,30" />
<Button Content="НАЧАТЬ УСТАНОВКУ" Command="{Binding GoToSettingsCommand}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Background="#8B5CF6" Foreground="White" FontWeight="Bold" FontSize="14" Padding="15" />
<Button Content="УДАЛИТЬ / ВОССТАНОВИТЬ" Command="{Binding StartUninstallCommand}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Background="Transparent" Foreground="#A0A0A0" BorderBrush="#333333" BorderThickness="1"
FontWeight="Bold" FontSize="12" Padding="12" />
</StackPanel>
</Grid>
<!-- ================================================= -->
<!-- 3. SETTINGS -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepSettings}" Margin="40">
<Button Content="← Назад" Command="{Binding GoBackToHomeCommand}" Background="Transparent"
Foreground="#777777" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBlock Text="Параметры" FontSize="28" FontWeight="Bold" Margin="0,45,0,0" />
<StackPanel Spacing="15" Margin="0,105,0,0">
<CheckBox Content="Блокировать автоматические обновления Spotify" IsChecked="{Binding BlockUpdates}" FontSize="14" />
<CheckBox Content="Скрыть раздел с подкастами" IsChecked="{Binding HidePodcasts}" FontSize="14" />
<CheckBox Content="Удалить кнопки покупки Premium" IsChecked="{Binding HidePremium}" FontSize="14" />
<CheckBox Content="Очистить кэш перед установкой" IsChecked="{Binding ClearCache}" FontSize="14" />
</StackPanel>
<Button Content="УСТАНОВИТЬ" Command="{Binding StartInstallCommand}" HorizontalAlignment="Right"
VerticalAlignment="Bottom" Background="White" Foreground="Black" FontWeight="Black"
FontSize="14" Padding="30,15" />
</Grid>
<!-- ================================================= -->
<!-- 4. INSTALLATION PROGRESS -->
<!-- ================================================= -->
<Grid IsVisible="{Binding IsStepProgress}" Background="#050505" ZIndex="5">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="20">
<ProgressBar Width="200" Height="4" IsIndeterminate="True" Foreground="#8B5CF6" Background="#18181D"/>
<TextBlock Text="Выполнение скриптов..." Foreground="#888888" FontSize="14" HorizontalAlignment="Center" />
</StackPanel>
</Grid>
<!-- ================================================= -->
<!-- 5. POPUP NOTIFICATION -->
<!-- ================================================= -->
<Grid IsVisible="{Binding ShowPopup}" Background="#D9000000" ZIndex="20">
<Border Width="400" Padding="30" Background="#111111" BorderBrush="#2A2A2A" BorderThickness="1"
CornerRadius="12" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel>
<TextBlock Text="{Binding PopupTitle}" Foreground="{Binding PopupColor}" FontSize="18"
FontWeight="Black" Margin="0,0,0,10" />
<TextBlock Text="{Binding PopupMessage}" Foreground="#CCCCCC" FontSize="14"
TextWrapping="Wrap" Margin="0,0,0,25" />
<Button Content="ОК" Command="{Binding ClosePopupCommand}" HorizontalAlignment="Right"
Background="#333333" Foreground="White" FontWeight="Bold" Padding="25,10" />
</StackPanel>
</Border>
</Grid>
</Grid>
</Window>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="SpotXInstaller.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>