Compare commits

..

11 Commits

Author SHA1 Message Date
github-actions ad1df3227b chore(release): 1.8.0 2025-11-07 14:32:28 +00:00
Achies 05f132ab1b Merge pull request #10 from achiez/1.8.0
1.8.0
2025-11-07 16:32:21 +02:00
achiez ee858921dc (chore) Prepare 1.8.0 Release 2025-11-07 16:31:37 +02:00
achiez 750292bfba Prepare for 1.8.0 release: Simplified CI/CD and localization updates
- Replaced old build-and-release.yml with a streamlined process in build-release.yml, simplifying release creation with dotnet publish, changelog conversion, and GitHub release automation.
- Added prepare-release.yml to automate release preparation, including version extraction, update.xml generation, and tagging.
- Introduced changelog for version 1.8.0 with a new HTML file (1.8.0.html) and external links to detailed updates.
- Updated NebulaAuth.sln to include the new changelog file.
- Refactored SetAccountPasswordsVM.cs to use localization for success messages and added a helper method for retrieving localized strings.
- Enhanced localization.loc.json with new strings for SetAccountPasswordsVM and improved formatting by removing duplicates.
- Improved changelog HTML structure and styles for better readability and mobile responsiveness.
- Performed minor code cleanups and formatting adjustments.
2025-11-07 16:31:01 +02:00
achiez 982bb4d0c8 [chore] Perform ReSharper cleanup 2025-11-07 10:35:10 +02:00
achiez 8e5fea54cd Added MAAC persistence support and password management features
- Introduced `MAACStorage` for managing multi-account auto-confirmers persistence
- Added `SetAccountPasswordsView` dialog for managing account passwords setup
- Enhanced `Storage` with async methods for file operations
- Added `BoolToValueConverter` for flexible XAML data bindings
- Enhanced user experience in dialogs with `IsDefault` and `IsCancel` properties
2025-11-07 10:27:16 +02:00
achiez 882d39b8f3 Refactor and enhance UI, localization, and file name handling
- Refactored file handling: added backup directories, improved mafile naming strategy, and introduced renaming functionality.
- Enhanced UI: updated `MainWindow`, `SettingsView`, and dialogs with new controls, commands, and improved layouts.
- Introduced `TextFieldDialog` for user input with localization support.
- Improved localization: added new strings for buttons, errors, and tooltips. Removed all user-observed not localized strings
- Fixed clipboard handling logic in `ClipboardHelper`.
- Added 'Create Group' button in mafile's context menu and corresponding dialog view
- Implemented `RequiresAdminAccess` check before updating program and requesting previlegies
2025-11-06 18:44:43 +02:00
achiez ffcc7405a7 Refactor and enhance proxy and mafile handling
- Introduced `Filename` property in `Mafile` for better file management.
- Refactored `Storage` methods to support `Filename` and simplify `.mafile` handling.
- Enhanced `ProxyManagerView` with new controls for protocol and credentials display.
- Implemented `HintBox` control for displaying errors and tips in UI.
- Updated `MainWindow` with improved UI elements and event handling.
- Improved command execution checks in `MainVM` for better UX.
- Added localization strings for new features like proxy display options.
- General code cleanups and formatting improvements.
2025-11-03 22:44:46 +02:00
achiez b3e7436e95 Remove FontScaleWindow.cs and residual related code 2025-10-29 11:17:25 +02:00
achiez d319fc19f9 Refine UX, fix auth crash, and revert to .NET 8.0
- Reverted target framework to .NET 8.0 for stability.
- Added autofocus to all dialog input fields (e.g. password dialogs).
- Fixed crash when relogging into accounts without Steam Guard and added proper error message.
- Unified and improved “Confirm Action” dialog design and text clarity.
- Added setting to disable ripple effect animations for better performance.
- Reduced minimum auto-confirmation timer interval from 10s to 5s.
- App now auto-selects the most appropriate language on first launch.
- Added trimming to proxy parse input
- Fixed incorrect hint about maFile binding when using login-based mode.
- Added ESC key behavior to remove focus from account search
- Minor UI polish and cleanup across dialogs.
2025-10-08 18:08:38 +03:00
achiez a56757302e Resolved issue where a "Confirmation Error" notification appeared despite the confirmation being successful 2025-07-19 00:11:42 +03:00
85 changed files with 2634 additions and 1204 deletions
-108
View File
@@ -1,108 +0,0 @@
name: Build and Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.ref == 'refs/heads/master'
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: "8.x"
- name: Restore dependencies
run: dotnet restore NebulaAuth.sln
- name: Build
run: dotnet build NebulaAuth.sln --configuration Release
- name: Get version from assembly
id: get-version
shell: pwsh
run: |
$content = Get-Content -Path "NebulaAuth/NebulaAuth.csproj" -Raw
$version = [regex]::Match($content, '<AssemblyVersion>(.*?)<\/AssemblyVersion>').Groups[1].Value
Write-Output "VERSION=$version" >> $env:GITHUB_ENV
- name: Check if tag exists
id: tag_exists
run: |
if (git tag -l | Select-String -Pattern "^${env:VERSION}$") {
Write-Output "Version $env:VERSION already exists."
exit 1
}
- name: Check changelog
run: |
if (-not (Test-Path "changelog/${env:VERSION}.html")) {
Write-Output "Changelog file changelog/${env:VERSION}.html does not exist."
exit 1
}
- name: Insert date into changelog
run: |
$date = Get-Date -Format "dd.MM.yyyy"
(Get-Content "changelog/${env:VERSION}.html") -replace '(?<=<div class="date">).*?(?=</div>)', $date | Set-Content "changelog/${env:VERSION}.html"
- name: Extract changelog description
id: extract_description
run: |
$description = (Get-Content "changelog/${env:VERSION}.html" | Select-String -Pattern '(?<=<div class="description">).*?(?=</div>)' | ForEach-Object { $_.Matches.Value }) -replace '<br\/>', "`n"
Write-Output "DESCRIPTION=$description" >> $env:GITHUB_ENV
- name: Create ZIP
run: |
New-Item -ItemType Directory -Path release -Force
Compress-Archive -Path NebulaAuth/bin/Release -DestinationPath release/NebulaAuth.${env:VERSION}.zip
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.VERSION }}
release_name: NebulaAuth ${{ env.VERSION }}
body: |
${{ env.DESCRIPTION }}
draft: false
prerelease: false
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: release/NebulaAuth.${{ env.VERSION }}.zip
asset_name: NebulaAuth.${{ env.VERSION }}.zip
asset_content_type: application/zip
- name: Update XML and Changelog html
shell: pwsh
run: |
$xmlContent = @"
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>${env:VERSION}.0</version>
<url>https://github.com/${env:GITHUB_REPOSITORY}/releases/download/${env:VERSION}/NebulaAuth.${env:VERSION}.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/${env:VERSION}.html</changelog>
<mandatory>false</mandatory>
</item>
"@
$xmlContent | Out-File -FilePath update.xml -Encoding UTF8 -Force
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git add changelog/${env:VERSION}.html update.xml
git commit -m "Update version to ${env:VERSION} and add changelog"
git push origin master
+78
View File
@@ -0,0 +1,78 @@
name: Publish release
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
release:
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract version
id: ver
run: echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
shell: bash
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build NebulaAuth
run: dotnet publish src/NebulaAuth/NebulaAuth.csproj -c Release -o build
- name: Package release
run: |
cd build
powershell Compress-Archive * "../NebulaAuth.${env:GITHUB_REF_NAME}.zip"
shell: pwsh
- name: Install pandoc
run: |
choco install pandoc -y
- name: Convert changelog HTML to Markdown (ul-only)
id: changelog
shell: bash
run: |
version="${{ steps.ver.outputs.version }}"
file="changelog/${version}.html"
echo "Looking for file: $file"
if [ -f "$file" ]; then
echo "✅ File found, extracting <ul> section"
sed -n '/<ul>/,/<\/ul>/p' "$file" > body.html
echo "Converting with pandoc..."
pandoc body.html -f html -t markdown --wrap=none -o changelog.md
echo "-------- Converted changelog.md --------"
cat changelog.md
echo "----------------------------------------"
echo "body<<EOF" >> $GITHUB_OUTPUT
cat changelog.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "❌ Changelog not found for version $version"
echo "body=No changelog found for $version" >> $GITHUB_OUTPUT
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.version }}
name: Release ${{ env.RELEASE_NAME }}
body: ${{ steps.changelog.outputs.body }}
files: |
NebulaAuth.${{ steps.ver.outputs.version }}.zip
env:
RELEASE_NAME: ${{ steps.ver.outputs.version }}
+65
View File
@@ -0,0 +1,65 @@
name: Prepare release
on:
push:
branches: [ master ]
paths:
- 'src/NebulaAuth/NebulaAuth.csproj'
workflow_dispatch:
jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_TOKEN }}
- name: Read version from csproj
id: ver
shell: bash
run: |
VERSION=$(grep -oPm1 "(?<=<AssemblyVersion>)[^<]+" src/NebulaAuth/NebulaAuth.csproj)
echo "Detected version $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Generate update.xml
run: |
cat > NebulaAuth/update.xml <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>${{ steps.ver.outputs.version }}</version>
<url>https://github.com/${{ github.repository }}/releases/download/${{ steps.ver.outputs.version }}/NebulaAuth.${{ steps.ver.outputs.version }}.zip</url>
<changelog>https://achiez.github.io/${{ github.repository }}/changelog/${{ steps.ver.outputs.version }}.html</changelog>
<mandatory>false</mandatory>
</item>
EOF
echo "Generated NebulaAuth/update.xml:"
cat NebulaAuth/update.xml
- name: Check if update.xml changed
id: diff
run: |
if git diff --exit-code NebulaAuth/update.xml >/dev/null 2>&1; then
echo "no_changes=true" >> $GITHUB_OUTPUT
else
echo "no_changes=false" >> $GITHUB_OUTPUT
fi
- name: Commit and tag
if: steps.diff.outputs.no_changes == 'false'
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add NebulaAuth/update.xml
git commit -m "chore(release): ${{ steps.ver.outputs.version }}"
git tag -a "${{ steps.ver.outputs.version }}" -m "Release ${{ steps.ver.outputs.version }}"
git push origin HEAD:master --tags
- name: Skip message
if: steps.diff.outputs.no_changes == 'true'
run: echo "No changes in version or update.xml — skipping release preparation."
+3 -1
View File
@@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
todo/
+2
View File
@@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
changelog\1.7.1.html = changelog\1.7.1.html
changelog\1.7.2.html = changelog\1.7.2.html
changelog\1.7.3.html = changelog\1.7.3.html
changelog\1.7.4.html = changelog\1.7.4.html
changelog\1.8.0.html = changelog\1.8.0.html
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamLibForked", "src\SteamLibForked\SteamLibForked.csproj", "{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}"
+7 -8
View File
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.7.3.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.7.3/NebulaAuth.1.7.3.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.7.3.html</changelog>
<mandatory>false</mandatory>
</item>
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.8.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.0/NebulaAuth.1.8.0.zip</url>
<changelog>https://achiez.github.io/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.0.html</changelog>
<mandatory>false</mandatory>
</item>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.7.4</div>
<div class="date">19.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>FIX:</b> Resolved issue where a "Confirmation Error" notification appeared despite the confirmation being successful.</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.8.0</div>
<div class="date">07.11.2025</div>
<div class="description">
<ul>
<li> Major update: Full changelog is available here <a href="https://teletype.in/@achies_raw/nebula-1-8-0-eng">ENG</a> | <a href="https://teletype.in/@achies_raw/nebula-1-8-0-rus">RUS</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
+1
View File
@@ -33,6 +33,7 @@
</Style>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
+3 -10
View File
@@ -1,11 +1,9 @@
using System;
using System.IO;
using System.Linq;
using System.Windows;
using AchiesUtilities.Extensions;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
namespace NebulaAuth;
@@ -21,14 +19,9 @@ public partial class App
LocManager.Init();
LocManager.SetApplicationLocalization(Settings.Instance.Language);
Shell.Initialize();
var files = 0;
if (Directory.Exists(Storage.MAFILE_F))
files = Directory.GetFiles(Storage.MafileFolder)
.Count(f => Path.GetExtension(f).EqualsIgnoreCase(".mafile"));
var threads = files > 0 ? files / 100 + 1 : 1;
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
await Storage.Initialize(threads);
MAACStorage.Initialize();
var mainWindow = new MainWindow();
Current.MainWindow = mainWindow;
mainWindow.Show();
@@ -3,7 +3,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace NebulaAuth.Theme.Controls;
namespace NebulaAuth;
public class CodeProgressBar : ProgressBar
{
@@ -13,7 +13,7 @@ public class CodeProgressBar : ProgressBar
public static readonly DependencyProperty MaxTimeProperty =
DependencyProperty.Register(nameof(MaxTime), typeof(double), typeof(CodeProgressBar),
new PropertyMetadata(30.0)); // По умолчанию 30 сек
new PropertyMetadata(30.0));
public double TimeRemaining
{
+52
View File
@@ -0,0 +1,52 @@
<UserControl x:Class="NebulaAuth.HintBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance nebulaAuth:HintBox}"
d:DesignHeight="100" d:DesignWidth="400">
<Border Padding="5"
BorderThickness="1"
CornerRadius="12"
Visibility="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource NullableToVisibilityConverter}}">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Height="22" Width="22"
VerticalAlignment="Center"
Margin="5,0,0,0"
Foreground="{Binding IconBrush, RelativeSource={RelativeSource AncestorType=UserControl}}"
Kind="{Binding IconKind, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<TextBlock Grid.Column="1"
FontSize="{Binding FontSize, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="WrapWithOverflow"
VerticalAlignment="Center"
Margin="10,10,10,10"
Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Button Grid.Column="2"
Visibility="{Binding ShowCloseButton, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{StaticResource MaterialDesignToolButton}"
Margin="0,0,5,0"
Padding="2">
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
</Button>
</Grid>
</Border>
</UserControl>
+98
View File
@@ -0,0 +1,98 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using CommunityToolkit.Mvvm.Input;
using MaterialDesignThemes.Wpf;
namespace NebulaAuth;
public partial class HintBox : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(HintBox));
public static readonly DependencyProperty SeverityProperty =
DependencyProperty.Register(nameof(Severity), typeof(HintBoxSeverity), typeof(HintBox),
new PropertyMetadata(HintBoxSeverity.Info, OnSeverityChanged));
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.Register(nameof(ShowCloseButton), typeof(bool), typeof(HintBox),
new PropertyMetadata(false));
public static readonly DependencyProperty CloseCommandProperty =
DependencyProperty.Register(nameof(CloseCommand), typeof(ICommand), typeof(HintBox),
new PropertyMetadata(null));
public string Text
{
get => (string) GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public HintBoxSeverity Severity
{
get => (HintBoxSeverity) GetValue(SeverityProperty);
set => SetValue(SeverityProperty, value);
}
public ICommand CloseCommand
{
get => (ICommand) GetValue(CloseCommandProperty) ?? InternalCloseCommand;
set => SetValue(CloseCommandProperty, value);
}
public bool ShowCloseButton
{
get => (bool) GetValue(ShowCloseButtonProperty);
set => SetValue(ShowCloseButtonProperty, value);
}
private ICommand InternalCloseCommand { get; }
public PackIconKind IconKind { get; private set; }
public Brush IconBrush { get; private set; }
public HintBox()
{
InitializeComponent();
ApplySeverityVisuals();
InternalCloseCommand = new RelayCommand(Close);
}
public event RoutedEventHandler Closed;
private static void OnSeverityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((HintBox) d).ApplySeverityVisuals();
}
private void ApplySeverityVisuals()
{
switch (Severity)
{
case HintBoxSeverity.Error:
IconKind = PackIconKind.ErrorOutline;
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
break;
default:
IconKind = PackIconKind.InfoCircleOutline;
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
break;
}
}
private void Close()
{
Visibility = Visibility.Collapsed;
Closed?.Invoke(this, new RoutedEventArgs());
}
}
public enum HintBoxSeverity
{
Info,
Error
}
@@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class BoolToMafileNamingConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not bool boolValue)
return null;
return boolValue ? "Login" : "Steam ID";
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class BoolToValueConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not bool b) return Binding.DoNothing;
if (parameter is Array)
{
var arr = (Array) parameter;
if (arr.Length == 0) return Binding.DoNothing;
var first = arr.GetValue(0);
var second = arr.Length > 1 ? arr.GetValue(1) : null;
return b ? first : second;
}
return b ? parameter : null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+3 -1
View File
@@ -8,11 +8,13 @@
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
<converters:ProxyTextConverter x:Key="ProxyTextConverter" />
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter" />
<converters:ProxyDataTextMultiConverter x:Key="ProxyDataTextMultiConverter" />
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter" />
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
<converters:BoolToMafileNamingConverter x:Key="BoolToMafileNamingConverter" />
<converters:BoolToValueConverter x:Key="BoolToValueConverter" />
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Text;
using System.Windows.Data;
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
@@ -33,11 +34,50 @@ public class ProxyDataTextConverter : IValueConverter
return string.Empty;
}
return $"{p.Address}:{p.Port}";
return $"{p.Protocol.ToString().ToLowerInvariant()}://{p.Address}:{p.Port}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public class ProxyDataTextMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var value = values[0] as ProxyData;
var displayProtocol = values.Length > 1 && values[1] is bool dp && dp;
var displayCredentials = values.Length > 2 && values[2] is bool dc && dc;
if (value == null)
{
return string.Empty;
}
var sb = new StringBuilder();
if (displayProtocol)
{
sb.Append(value.Protocol.ToString().ToLowerInvariant());
sb.Append("://");
}
sb.Append(value.Address);
sb.Append(':');
sb.Append(value.Port);
if (displayCredentials && value.AuthEnabled)
{
sb.Append(':');
sb.Append(value.Username);
sb.Append(':');
sb.Append(value.Password);
}
return sb.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+29 -19
View File
@@ -13,25 +13,6 @@ namespace NebulaAuth.Core;
public static class DialogsController
{
#region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
{
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
}
//public static async Task<string?> ShowTextFieldDialog(string? msg = null)
//{
// var content = msg == null ? new TextFieldDialog() : new TextFieldDialog(msg);
// var result = await DialogHost.Show(content);
// return result as string;
//}
#endregion
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null)
{
var vm = new LoginAgainVM
@@ -121,4 +102,33 @@ public static class DialogsController
vm?.Dispose();
}
}
public static async Task ShowSetAccountsPasswordDialog()
{
var vm = new SetAccountPasswordsVM();
var dialog = new SetAccountPasswordsView
{
DataContext = vm
};
await DialogHost.Show(dialog);
}
#region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
{
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
}
public static async Task<string?> ShowTextFieldDialog(string? title = null, string? msg = null)
{
var content = new TextFieldDialog(title, msg);
var result = await DialogHost.Show(content);
return result as string;
}
#endregion
}
+1 -1
View File
@@ -29,7 +29,7 @@ public static class TrayManager
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("Выйти", null!, OnExitClick);
contextMenu.Items.Add("Exit", null!, OnExitClick);
_notifyIcon.ContextMenuStrip = contextMenu;
+19 -3
View File
@@ -1,7 +1,6 @@
using System;
using System.IO;
using AutoUpdaterDotNET;
using NebulaAuth.Model;
namespace NebulaAuth.Core;
@@ -15,11 +14,28 @@ public static class UpdateManager
var jsonPath = Path.Combine(Environment.CurrentDirectory, "update-settings.json");
AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath);
AutoUpdater.ShowSkipButton = false;
if (Settings.Instance.AllowAutoUpdate)
AutoUpdater.UpdateMode = Mode.ForcedDownload;
AutoUpdater.RunUpdateAsAdmin = RequiresAdminAccess();
AutoUpdater.Start(UPDATE_URL);
}
private static bool RequiresAdminAccess()
{
try
{
var testFile = Path.Combine(Environment.CurrentDirectory, "test.tmp");
using (File.Create(testFile))
{
}
File.Delete(testFile);
return false;
}
catch (UnauthorizedAccessException)
{
return true;
}
}
//static UpdateManager()
//{
+171 -102
View File
@@ -1,24 +1,25 @@
<w:FontScaleWindow x:Class="NebulaAuth.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="clr-namespace:NebulaAuth.Theme"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:windowStyle="clr-namespace:NebulaAuth.Theme.WindowStyle"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
xmlns:controls="clr-namespace:NebulaAuth.Theme.Controls"
WindowStartupLocation="CenterScreen"
MinHeight="500" MinWidth="500" DefaultFontSize="18" ScaleCoefficient="0.4"
Title="NebulaAuth" Height="800" Width="730"
Style="{StaticResource MainWindow}"
RenderOptions.BitmapScalingMode="HighQuality"
FontFamily="{md:MaterialDesignFont}"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:MainVM}">
<Window x:Class="NebulaAuth.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:windowStyle="clr-namespace:NebulaAuth.Theme.WindowStyle"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
WindowStartupLocation="CenterScreen"
MinHeight="500" MinWidth="500"
Title="NebulaAuth" Height="800" Width="730"
Style="{StaticResource MainWindow}"
RenderOptions.BitmapScalingMode="HighQuality"
FontFamily="{md:MaterialDesignFont}"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:MainVM}"
md:RippleAssist.IsDisabled="{Binding Settings.RippleDisabled}"
FontSize="18">
<b:Interaction.Behaviors>
<windowStyle:WindowChromeRenderedBehavior />
</b:Interaction.Behaviors>
@@ -27,7 +28,14 @@
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
Key="V"
Modifiers="Control" />
<KeyBinding Command="{x:Static ApplicationCommands.Find}"
Key="F"
Modifiers="Control" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="Find"
Executed="FocusSearchBox" />
</Window.CommandBindings>
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True"
DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
<md:DialogHost DialogOpened="DialogHost_DialogOpened"
@@ -57,12 +65,13 @@
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123"
Opacity="0.9" Visibility="Hidden" />
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1"
w:FontScaleWindow.ResizeFont="True" Grid.RowSpan="3" Visibility="Hidden"
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" Grid.RowSpan="3" Visibility="Hidden"
VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}" />
<TextBlock FontSize="24" FontWeight="Bold" Foreground="#9a65b8"
Text="{Tr MainWindow.Global.DragNDropHint}" />
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36"
Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray>
<ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch"
@@ -71,17 +80,23 @@
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}"
Command="{Binding AddMafileCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.File.Remove}"
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}"
Command="{Binding RemoveMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}"
Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.ProxyManager}"
Command="{Binding OpenProxyManagerCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}"
Command="{Binding OpenSettingsDialogCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.Title}">
<MenuItem Header="{Tr MainWindow.Menu.File.Other.SetPasswords}"
Command="{Binding DebugCommand}" />
</MenuItem>
</MenuItem>
</Menu>
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<Menu>
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}"
Command="{Binding LinkAccountCommand}" />
@@ -91,18 +106,18 @@
Command="{Binding RemoveAuthenticatorCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.Account.RefreshSession}"
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}"
Command="{Binding RefreshSessionCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.Account.LoginAgain}"
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}"
Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
<ComboBox ToolTip="{Tr MainWindow.AppBar.GroupToolTip}"
md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
MinWidth="100"
MaxWidth="200"
Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Groups}"
SelectedValue="{Binding SelectedGroup}"
md:TextFieldAssist.HasClearButton="True">
@@ -123,6 +138,7 @@
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
MinWidth="80"
MaxWidth="200"
Margin="8,0,8,0"
VerticalAlignment="Center"
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
@@ -146,6 +162,7 @@
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource ErrorBrush}" Margin="3"
ToolTipService.InitialShowDelay="300">
@@ -173,6 +190,7 @@
ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}"
ToolTipService.InitialShowDelay="300"
Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Separator />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}"
IsEnabled="{Binding IsMafileSelected}" Margin="2"
Style="{StaticResource MaterialDesignFlatToggleButton}"
@@ -249,9 +267,17 @@
TextWrapping="WrapWithOverflow" FontSize="16"
Text="{Tr MainWindow.LeftPart.NoMafiles}" />
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="2"
<ListBox FontSize="17" Margin="2"
x:Name="MafileListBox"
SelectionMode="Single"
ItemsSource="{Binding MaFiles}"
SelectedValue="{Binding SelectedMafile}">
SelectedValue="{Binding SelectedMafile}"
PreviewMouseDown="MafileListBox_OnPreviewMouseDown">
<ListBox.Resources>
<DiscreteObjectKeyFrame x:Key="groupsProxy" Value="{Binding Groups}" />
<DiscreteObjectKeyFrame x:Key="listBoxProxy"
Value="{Binding ElementName=MafileListBox}" />
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource MaterialDesignListBoxItem}">
@@ -261,7 +287,7 @@
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid ToolTip="{Binding Filename}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
@@ -296,13 +322,20 @@
<KeyBinding Key="X" Modifiers="Control"
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
<KeyBinding Modifiers="Control+Shift" Key="C"
Command="{Binding DataContext.CopyPasswordCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
</ListBox.InputBindings>
<FrameworkElement.ContextMenu>
<ContextMenu>
<ContextMenu Tag="{Binding Groups}">
<MenuItem InputGestureText="Ctrl+C"
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
Command="{Binding CopyLoginCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
InputGestureText="CTRL+⇧+C" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
Command="{Binding CopySteamIdCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
@@ -310,8 +343,33 @@
Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}"
Command="{Binding CopyMafileCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}"
ItemsSource="{Binding Groups}">
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}">
<MenuItem.ItemsSource>
<CompositeCollection>
<MenuItem
Command="{Binding CreateGroupCommand}"
CommandParameter="{Binding Value.SelectedValue, Source={StaticResource listBoxProxy}}">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<md:PackIcon Kind="PlusCircle"
VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center"
Margin="5,0,0,0" Grid.Column="1"
Text="{Tr MainWindow.ContextMenus.Mafile.CreateGroup}" />
</Grid>
</MenuItem.Header>
</MenuItem>
<Separator />
<CollectionContainer
Collection="{Binding Value, Source={StaticResource groupsProxy}}" />
</CompositeCollection>
</MenuItem.ItemsSource>
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}"
TargetType="{x:Type MenuItem}">
@@ -333,9 +391,6 @@
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}"
Command="{Binding Path=RemoveGroupCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
@@ -343,87 +398,101 @@
</ListBox>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10"
FontSize="16"
md:TextFieldAssist.HasClearButton="True" Grid.Row="1" Margin="10,5,10,10"
md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}"
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
x:Name="SearchField"
KeyDown="SearchField_OnKeyDown" />
</Grid>
</Border>
</Grid>
<md:Card BorderThickness="1" Style="{StaticResource MaterialDesignOutlinedCard}" Grid.Column="1"
Margin="10,10,15,10" UniformCornerRadius="12" Opacity="{Binding Settings.RightOpacity}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="115*" />
<RowDefinition Height="436*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Row="0">
<Grid Grid.Column="1">
<Border Background="{DynamicResource MaterialDesign.Brush.Card.Background}"
Opacity="{Binding Settings.RightOpacity}"
BorderThickness="1"
Margin="10,10,15,10" CornerRadius="12" VerticalAlignment="Stretch">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.3" />
</Border.BorderBrush>
</Border>
<md:Card Margin="10,10,15,10" Padding="2" UniformCornerRadius="12"
Background="Transparent">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="115*" />
<RowDefinition Height="436*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True"
IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF"
<Grid Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox FontSize="24"
IsReadOnly="True" HorizontalContentAlignment="Center"
Background="#00FFFFFF"
Style="{StaticResource MaterialDesignTextBox}"
Style="{StaticResource MaterialDesignTextBox}"
Text="{Binding Code, FallbackValue=Code}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
</TextBox.InputBindings>
</TextBox>
<controls:CodeProgressBar
Visibility="{Binding SelectedMafile, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="1" Height="4" Style="{StaticResource MaterialDesignLinearProgressBar}"
MaxTime="30" TimeRemaining="{Binding CodeProgress, Mode=OneWay}" />
Text="{Binding Code, FallbackValue=Code}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
</TextBox.InputBindings>
</TextBox>
<nebulaAuth:CodeProgressBar
Visibility="{Binding SelectedMafile, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="1" Height="4"
Style="{StaticResource MaterialDesignLinearProgressBar}"
MaxTime="30" TimeRemaining="{Binding CodeProgress, Mode=OneWay}" />
</Grid>
<Button IsEnabled="{Binding IsMafileSelected}"
FontSize="14"
Grid.Row="1"
Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="0,10,0,10"
HorizontalAlignment="Center"
Content="{Tr MainWindow.RightPart.LoadConfirmations}"
Command="{Binding GetConfirmationsCommand}"
md:ButtonProgressAssist.IsIndeterminate="True"
md:ButtonProgressAssist.IsIndicatorVisible="{Binding GetConfirmationsCommand.IsRunning}"
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}" />
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
FontSize="16"
Margin="10,10,10,10"
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
ItemsSource="{Binding Confirmations}" Grid.RowSpan="2" />
<Button FontSize="16"
Style="{StaticResource MaterialDesignFlatButton}" Grid.Row="4"
Command="{Binding ConfirmLoginCommand}"
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
</Grid>
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Grid.Row="1"
Style="{StaticResource MaterialDesignOutlinedButton}" Margin="0,10,0,10"
HorizontalAlignment="Center"
Content="{Tr MainWindow.RightPart.LoadConfirmations}"
Command="{Binding GetConfirmationsCommand}"
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}" />
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True"
Margin="10,10,10,10"
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
ItemsSource="{Binding Confirmations}" Grid.RowSpan="2" />
<Button Style="{StaticResource MaterialDesignFlatButton}" Grid.Row="4"
w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True"
Command="{Binding ConfirmLoginCommand}"
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
</Grid>
</md:Card>
</md:Card>
</Grid>
</Grid>
<Border Grid.Row="2" BorderBrush="#1e1e24">
<Border.Background>
<SolidColorBrush Opacity="1" Color="{DynamicResource Base300Color}" />
</Border.Background>
<Grid>
<ToolBarPanel TextElement.Foreground="{DynamicResource SecondaryContentBrush}"
Orientation="Horizontal" Margin="5" w:FontScaleWindow.Scale="0.73"
w:FontScaleWindow.ResizeFont="True">
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Account}" />
<TextBlock Text="{Binding SelectedMafile.AccountName}" />
<TextBlock Text="|" Margin="10,0,10,0" />
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Group}" />
<TextBlock FontWeight="Normal"
Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
Orientation="Horizontal" Margin="5">
<TextBlock FontSize="15" Text="{Tr MainWindow.Footer.AccountsCount}" />
<TextBlock FontSize="15" Text="{Binding MaFiles.Count}" />
<TextBlock FontSize="15" Text="|" Margin="10,0,10,0" />
<TextBlock FontSize="15" Text="{Tr MainWindow.Footer.SelectedAccount}" />
<TextBlock FontSize="15"
Text="{Binding SelectedMafile.AccountName, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
<!--<Button Command="{Binding DebugCommand}">Debug</Button>-->
</ToolBarPanel>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="16"
Margin="0,0,10,0">
<Hyperlink
NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"
@@ -438,4 +507,4 @@
</Grid>
</md:DialogHost>
</Border>
</w:FontScaleWindow>
</Window>
+24
View File
@@ -2,6 +2,7 @@
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using MaterialDesignThemes.Wpf;
@@ -51,6 +52,29 @@ public partial class MainWindow
}
}
private void SearchField_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Escape || !SearchField.IsFocused) return;
Keyboard.ClearFocus();
}
private void MafileListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) return;
if (ItemsControl.ContainerFromElement((ListBox) sender, (DependencyObject) e.OriginalSource) is ListBoxItem
{
IsSelected: true
})
{
e.Handled = true;
}
}
private void FocusSearchBox(object sender, ExecutedRoutedEventArgs e)
{
SearchField.Focus();
}
#region Dran'n'Drop
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
+9 -1
View File
@@ -20,8 +20,16 @@ public partial class Mafile : MobileDataExtended
set => SetProperty(ref _linkedClient, value);
}
[JsonIgnore] private PortableMaClient? _linkedClient;
[JsonIgnore]
public string? Filename
{
get => _filename;
set => SetProperty(ref _filename, value);
}
private string? _filename;
[JsonIgnore] private PortableMaClient? _linkedClient;
public void SetSessionData(MobileSessionData? sessionData)
{
+144
View File
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using Newtonsoft.Json;
namespace NebulaAuth.Model.MAAC;
public static class MAACStorage
{
private static Dictionary<string, StoredClient> Clients { get; set; } = [];
static MAACStorage()
{
}
private static void ClientsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
Clients.Clear();
}
else if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (item is Mafile {Filename: not null} mafile)
{
if (mafile.LinkedClient != null)
mafile.LinkedClient.PropertyChanged += LinkedClientOnPropertyChanged;
Clients.Add(mafile.Filename, new StoredClient
{
AutoConfirmMarket = mafile.LinkedClient?.AutoConfirmMarket ?? false,
AutoConfirmTrades = mafile.LinkedClient?.AutoConfirmTrades ?? false
});
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
if (item is Mafile {Filename: not null} mafile)
{
if (mafile.LinkedClient != null)
mafile.LinkedClient.PropertyChanged -= LinkedClientOnPropertyChanged;
Clients.Remove(mafile.Filename);
}
}
}
Save();
}
private static void LinkedClientOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is not PortableMaClient client) return;
if (client.Mafile.Filename == null) return;
var anyChanges = false;
if (!Clients.TryGetValue(client.Mafile.Filename, out var storedClient))
{
client.PropertyChanged -= LinkedClientOnPropertyChanged;
return;
}
if (e.PropertyName == nameof(PortableMaClient.AutoConfirmMarket))
{
anyChanges = storedClient.AutoConfirmMarket != client.AutoConfirmMarket;
storedClient.AutoConfirmMarket = client.AutoConfirmMarket;
}
else if (e.PropertyName == nameof(PortableMaClient.AutoConfirmTrades))
{
anyChanges = storedClient.AutoConfirmTrades != client.AutoConfirmTrades;
storedClient.AutoConfirmTrades = client.AutoConfirmTrades;
}
if (anyChanges)
Save();
}
public static void Save()
{
var json = JsonConvert.SerializeObject(Clients, Formatting.Indented);
File.WriteAllText("maac.json", json);
}
public static void Initialize()
{
if (!File.Exists("maac.json")) return;
try
{
var json = File.ReadAllText("maac.json");
var clients = JsonConvert.DeserializeObject<Dictionary<string, StoredClient>>(json) ?? [];
foreach (var (fileName, storedClient) in clients)
{
var mafile = Storage.MaFiles.FirstOrDefault(x => x.Filename == fileName);
if (mafile == null) continue;
if (storedClient is {AutoConfirmMarket: false, AutoConfirmTrades: false}) continue;
if (MultiAccountAutoConfirmer.TryAddToConfirm(mafile) && mafile.LinkedClient != null)
{
mafile.LinkedClient.AutoConfirmMarket = storedClient.AutoConfirmMarket;
mafile.LinkedClient.AutoConfirmTrades = storedClient.AutoConfirmTrades;
Clients[fileName] = storedClient;
}
}
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Failed to load MAAC storage");
SnackbarController.SendSnackbar(
LocManager.GetCodeBehindOrDefault("FailedToLoadStorage", "MAAC", "FailedToLoadStorage"));
}
MultiAccountAutoConfirmer.Clients.CollectionChanged += ClientsOnCollectionChanged;
}
public static void NotifyMafilesRenamed(IDictionary<string, string> oldNewNames)
{
var updatedClients = new Dictionary<string, StoredClient>();
foreach (var (oldName, newName) in oldNewNames)
{
if (Clients.Remove(oldName, out var storedClient))
{
updatedClients[newName] = storedClient;
}
}
Clients = updatedClients;
Save();
}
private class StoredClient
{
public bool AutoConfirmMarket { get; set; }
public bool AutoConfirmTrades { get; set; }
}
}
@@ -25,7 +25,7 @@ public static class MultiAccountAutoConfirmer
Clients = [];
Timer = new Timer(TimerConfirm);
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
UpdateTimer();
UpdateTimer(true);
}
// ReSharper disable once AsyncVoidMethod //Already safe
@@ -120,8 +120,8 @@ public static class MultiAccountAutoConfirmer
return Lock.WriteLock(() =>
{
if (Clients.Contains(mafile)) return false;
Clients.Add(mafile);
mafile.LinkedClient = new PortableMaClient(mafile);
Clients.Add(mafile);
return true;
});
}
@@ -131,9 +131,9 @@ public static class MultiAccountAutoConfirmer
{
Lock.WriteLock(() =>
{
Clients.Remove(mafile);
mafile.LinkedClient?.Dispose();
mafile.LinkedClient = null;
Clients.Remove(mafile);
});
}
@@ -144,11 +144,17 @@ public static class MultiAccountAutoConfirmer
UpdateTimer();
}
private static void UpdateTimer()
private static void UpdateTimer(bool firstStart = false)
{
var timerInterval = Settings.Instance.TimerSeconds;
var intervalTimeSpan = TimeSpan.FromSeconds(timerInterval);
Timer.Change(intervalTimeSpan, intervalTimeSpan);
var dueTime = intervalTimeSpan;
if (firstStart)
{
dueTime = timerInterval >= 30 ? intervalTimeSpan : TimeSpan.FromSeconds(30);
}
Timer.Change(dueTime, intervalTimeSpan);
}
private static string GetLocalization(string key)
@@ -139,6 +139,10 @@ public partial class PortableMaClient : ObservableObject, IDisposable
await SteamTradeApi.Acknowledge(Client, Mafile.SessionData!.SessionId, _cts.Token);
await Task.Delay(10, _cts.Token);
}
else
{
return res;
}
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, conf,
Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
+20 -16
View File
@@ -20,11 +20,8 @@ namespace NebulaAuth.Model;
public static class MaClient
{
private static HttpClientHandler ClientHandler { get; }
private static HttpClient Client { get; }
private static DynamicProxy Proxy { get; }
public static ProxyData? DefaultProxy { get; set; }
@@ -40,21 +37,19 @@ public static class MaClient
public static void SetAccount(Mafile? account)
{
ClientHandler.CookieContainer.ClearAllCookies();
if (account != null)
if (account == null) return;
if (account.SessionData != null)
{
if (account.SessionData != null)
{
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData);
}
else
{
ClientHandler.CookieContainer.ClearSteamCookies();
ClientHandler.CookieContainer.AddMinimalMobileCookies();
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
}
Proxy.SetData(account.Proxy?.Data);
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData);
}
else
{
ClientHandler.CookieContainer.ClearSteamCookies();
ClientHandler.CookieContainer.AddMinimalMobileCookies();
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
}
Proxy.SetData(account.Proxy?.Data);
}
public static Task<IEnumerable<Confirmation>> GetConfirmations(Mafile mafile)
@@ -86,12 +81,17 @@ public static class MaClient
var res = await SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId,
mafile,
confirm);
if (!res && confirmation.ConfType == ConfirmationType.Trade)
{
Shell.Logger.Warn("Failed to send trade confirmation for {accountName}. Sending ack", mafile.AccountName);
await SteamTradeApi.Acknowledge(Client, mafile.SessionData.SessionId);
await Task.Delay(10);
}
else
{
return res;
}
return await SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId,
mafile,
@@ -119,6 +119,10 @@ public static class MaClient
await SteamTradeApi.Acknowledge(Client, mafile.SessionData.SessionId);
await Task.Delay(10);
}
else
{
return res;
}
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable,
mafile.SessionData!.SteamId,
@@ -0,0 +1,89 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
namespace NebulaAuth.Model.Mafiles;
public interface IMafileNamingStrategy
{
bool CanNameMafile(Mafile mafile);
string GetMafileName(Mafile mafile);
}
public class MafileNamingStrategy : IMafileNamingStrategy
{
public const string DEF_EXTENSION = ".mafile";
public static readonly IDictionary<string, Func<Mafile, string?>> Placeholders =
new Dictionary<string, Func<Mafile, string?>>
{
{"{steamid}", GetSteamId},
{"{login}", GetLogin},
{"{group}", GetGroup},
{"{servertime}", GetServerTime}
}.ToFrozenDictionary();
public static MafileNamingStrategy SteamId { get; } = new("{steamid}");
public static MafileNamingStrategy Login { get; } = new("{login}");
public string Pattern { get; }
public bool IncludeExtension { get; }
public MafileNamingStrategy(string pattern, bool includeExtension = true)
{
Pattern = pattern;
IncludeExtension = includeExtension;
}
public bool CanNameMafile(Mafile mafile)
{
if (string.IsNullOrWhiteSpace(Pattern)) return false;
var fileName = ApplyPatternSubstitution(mafile, Pattern, IncludeExtension);
return FileNameValidator.IsValidFileName(fileName);
}
public string GetMafileName(Mafile mafile)
{
if (!CanNameMafile(mafile))
throw new InvalidOperationException($"Cannot name mafile with the current pattern {Pattern}");
return ApplyPatternSubstitution(mafile, Pattern, IncludeExtension);
}
private static string ApplyPatternSubstitution(Mafile mafile, string pattern, bool includeExtension)
{
var result = pattern;
foreach (var placeholder in Placeholders)
{
if (result.Contains(placeholder.Key, StringComparison.OrdinalIgnoreCase))
{
var value = placeholder.Value(mafile);
result = result.Replace(placeholder.Key, value, StringComparison.OrdinalIgnoreCase);
}
}
return includeExtension ? result + DEF_EXTENSION : result;
}
private static string GetSteamId(Mafile mafile)
{
return mafile.SteamId.Steam64.ToString();
}
private static string GetLogin(Mafile mafile)
{
return mafile.AccountName;
}
private static string? GetGroup(Mafile mafile)
{
return mafile.Group;
}
private static string GetServerTime(Mafile mafile)
{
return mafile.ServerTime.ToString();
}
}
+4 -2
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using NebulaAuth.Model.Entities;
using NebulaAuth.Model.Exceptions;
using Newtonsoft.Json;
@@ -32,7 +33,7 @@ public static class NebulaSerializer
}
public static Mafile Deserialize(string cont)
public static Mafile Deserialize(string cont, string path)
{
var data = Serializer.Deserialize(cont);
var mobileData = data.Data;
@@ -41,12 +42,13 @@ public static class NebulaSerializer
throw new FormatException("Mafile is not extended data");
var props = info.UnusedProperties ?? new Dictionary<string, JProperty>();
var props = info.UnusedProperties ?? [];
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
var group = GetPropertyValue<string>("Group", props);
var password = GetPropertyValue<string>("Password", props);
var mafile = Mafile.FromMobileDataExtended((MobileDataExtended) mobileData, proxy, group, password);
mafile.Filename = Path.GetFileName(path);
if (!info.SteamIdValid)
throw new MafileNeedReloginException(mafile);
+4 -2
View File
@@ -46,7 +46,9 @@ public static class ProxyStorage
}
catch (Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке прокси");
Shell.Logger.Error(ex, "Error while loading proxies");
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("Error when loading proxies",
"ProxyStorage", "ErrorWhileLoadingProxies"));
SnackbarController.SendSnackbar(ex.Message);
}
}
@@ -93,7 +95,7 @@ public static class ProxyStorage
Save();
}
public static void OrderCollection() //RETHINK: maybe there is a better way to handle it
public static void SortCollection() //RETHINK: maybe there is a better way to handle it
{
var proxies = Proxies.OrderBy(p => p.Key)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+2 -2
View File
@@ -29,7 +29,7 @@ public partial class SessionHandler //API
//Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient
//RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour
mafile.SetSessionData(mafile.SessionData);
Storage.UpdateMafile(mafile);
await Storage.UpdateMafileAsync(mafile);
chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
}
@@ -53,6 +53,6 @@ public partial class SessionHandler //API
mafile.SetSessionData((MobileSessionData) result);
if (PHandler.IsPasswordSet)
mafile.Password = savePassword ? PHandler.Encrypt(password) : null;
Storage.UpdateMafile(mafile);
await Storage.UpdateMafileAsync(mafile);
}
}
+11 -3
View File
@@ -5,6 +5,7 @@ using System.IO;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using NebulaAuth.Utility;
using Newtonsoft.Json;
namespace NebulaAuth.Model;
@@ -28,6 +29,7 @@ public partial class Settings : ObservableObject
{
Instance = new Settings();
Instance.PropertyChanged += SettingsOnPropertyChanged;
Instance.Language = LanguageUtility.DetectPreferredLanguage();
return;
}
@@ -39,7 +41,8 @@ public partial class Settings : ObservableObject
}
catch (Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке настроек. Настройки были сброшены");
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("Error when loading settings", "Settings",
"ErrorWhileLoadingSettings"));
SnackbarController.SendSnackbar(ex.Message);
Instance = new Settings();
}
@@ -47,6 +50,7 @@ public partial class Settings : ObservableObject
Instance.PropertyChanged += SettingsOnPropertyChanged;
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Save();
@@ -68,8 +72,9 @@ public partial class Settings : ObservableObject
Instance.BackgroundOpacity = 1.0;
Instance.BackgroundGamma = 0.0;
Instance.LeftOpacity = 0.4;
Instance.RightOpacity = 0.8;
Instance.RightOpacity = 0.4;
Instance.ApplyBlurBackground = true;
Instance.RippleDisabled = false;
Save();
}
@@ -93,12 +98,15 @@ public partial class Settings : ObservableObject
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
[ObservableProperty] private double _leftOpacity = 0.4;
[ObservableProperty] private double _rightOpacity = 1.0;
[ObservableProperty] private double _rightOpacity = 0.4;
[ObservableProperty] private double _backgroundBlur;
[ObservableProperty] private double _backgroundOpacity = 1;
[ObservableProperty] private double _backgroundGamma;
[ObservableProperty] private bool _applyBlurBackground = true;
[ObservableProperty] private ThemeType _themeType = ThemeType.Default;
[ObservableProperty] private bool _rippleDisabled;
[ObservableProperty] private bool _proxyManagerDisplayProtocol;
[ObservableProperty] private bool _proxyManagerDisplayCredentials;
#endregion
}
+172 -132
View File
@@ -3,12 +3,15 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AchiesUtilities.Extensions;
using NebulaAuth.Model.Entities;
using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
using NebulaAuth.Model.Mafiles;
using SteamLib;
using SteamLib.Exceptions.Authorization;
using SteamLib.SteamMobile;
@@ -17,41 +20,28 @@ namespace NebulaAuth.Model;
public static class Storage
{
public const string MAFILE_F = "maFiles";
public const string REMOVED_F = "maFiles_removed";
private static int _duplicateFound;
public const string DIR_MAFILES = "maFiles";
public const string DIR_REMOVED_MAFILES = "maFiles_removed";
public const string DIR_BACKUP_MAFILES = "maFiles_backup";
public static string MafilesDirectory { get; } = Path.GetFullPath(DIR_MAFILES);
public static string RemovedMafilesDirectory { get; } = Path.GetFullPath(DIR_REMOVED_MAFILES);
public static string BackupMafilesDirectory { get; } = Path.GetFullPath(DIR_BACKUP_MAFILES);
public static int DuplicateFound => _duplicateFound;
public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
public static ObservableCollection<Mafile> MaFiles { get; private set; } = new();
static Storage()
{
}
public static ObservableCollection<Mafile> MaFiles { get; private set; } = [];
public static async Task Initialize(int threadCount, CancellationToken token = default)
{
if (!Directory.Exists(MafileFolder))
Directory.CreateDirectory(MafileFolder);
Directory.CreateDirectory(MafilesDirectory);
Directory.CreateDirectory(RemovedMafilesDirectory);
Directory.CreateDirectory(BackupMafilesDirectory);
if (!Directory.Exists(RemovedMafileFolder))
Directory.CreateDirectory(RemovedMafileFolder);
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
var files = Directory
.GetFiles(MafileFolder)
.GetFiles(MafilesDirectory)
.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile"))
.Order(comparer)
.ToList();
var hashNames = new ConcurrentDictionary<string, byte>();
var hashIds = new ConcurrentDictionary<SteamId, byte>();
var localList = new ConcurrentBag<Mafile>();
var processed = 0;
await Task.Run(() =>
{
return Parallel.ForEachAsync(files,
@@ -61,14 +51,6 @@ public static class Storage
try
{
var data = await ReadMafileAsync(file);
if (!hashNames.TryAdd(data.AccountName, 0) ||
(data.SessionData != null && !hashIds.TryAdd(data.SteamId, 0)))
{
Interlocked.Increment(ref _duplicateFound);
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
}
localList.Add(data);
}
catch (Exception ex)
@@ -114,31 +96,30 @@ public static class Storage
throw new FormatException("Can't generate code on this mafile", ex);
}
if (overwrite == false && File.Exists(CreatePathForMafile(data)))
if (overwrite == false && File.Exists(GetOrCreateMafilePath(data)))
{
throw new IOException("File already exist and overwrite is False");
throw new IOException("File already exist and overwrite is False"); //TODO: Custom Exception
}
SaveMafile(data);
}
public static Mafile ReadMafile(string path)
{
var str = File.ReadAllText(path);
return NebulaSerializer.Deserialize(str);
return NebulaSerializer.Deserialize(str, path);
}
public static async Task<Mafile> ReadMafileAsync(string path)
{
var str = await File.ReadAllTextAsync(path);
return NebulaSerializer.Deserialize(str);
return NebulaSerializer.Deserialize(str, path);
}
public static void SaveMafile(Mafile data)
{
var path = CreatePathForMafile(data);
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
@@ -154,134 +135,193 @@ public static class Storage
}
}
public static async Task SaveMafileAsync(Mafile data)
{
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
await File.WriteAllTextAsync(Path.GetFullPath(path), str);
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
if (existed != null)
{
var index = MaFiles.IndexOf(existed);
MaFiles[index] = data;
}
else
{
MaFiles.Add(data);
}
}
public static void UpdateMafile(Mafile data)
{
var path = CreatePathForMafile(data);
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static async Task UpdateMafileAsync(Mafile data)
{
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
await File.WriteAllTextAsync(Path.GetFullPath(path), str);
}
public static void MoveToRemoved(Mafile data)
{
var path = CreatePathForMafile(data);
var copyPath = Path.Combine(REMOVED_F, data.SteamId + ".mafile");
var copyPathCompleted = copyPath;
var sourcePath = GetOrCreateMafilePath(data);
var destinationPath = Path.Combine(DIR_REMOVED_MAFILES, data.Filename + ".mafile");
var destinationPathFinal = destinationPath;
var i = 0;
while (File.Exists(copyPathCompleted))
while (File.Exists(destinationPathFinal))
{
i++;
copyPathCompleted = copyPath + $" ({i})";
destinationPathFinal = destinationPath + $" ({i})";
}
File.Copy(path, copyPathCompleted, false);
File.Delete(path);
File.Copy(sourcePath, destinationPathFinal, false);
File.Delete(sourcePath);
MaFiles.Remove(data);
}
private static string CreatePathForMafile(Mafile data)
private static string GetOrCreateMafilePath(Mafile data)
{
var fileName = Settings.Instance.UseAccountNameAsMafileName
? CreateFileNameWithAccountName(data.AccountName)
: CreateFileNameWithSteamId(data.SteamId);
return Path.Combine(MafileFolder, fileName);
}
private static string CreateFileNameWithAccountName(string accountName)
{
return accountName + ".mafile";
}
private static string CreateFileNameWithSteamId(SteamId steamId)
{
return steamId.Steam64.Id + ".mafile";
}
public static string? TryFindMafilePath(Mafile data)
{
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
string? pathSteamId = null;
if (data.SessionData != null)
if (data.Filename != null)
{
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SteamId));
return Path.Combine(MafilesDirectory, data.Filename);
}
var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
var accountNameExist = File.Exists(pathFileName);
if (steamIdExist && accountNameExist)
{
return Settings.Instance.UseAccountNameAsMafileName ? pathFileName : pathSteamId;
}
if (steamIdExist ^ accountNameExist)
{
return steamIdExist ? pathSteamId : pathFileName;
}
return null;
var fileName = CreateMafileFileName(data, Settings.Instance.UseAccountNameAsMafileName);
data.Filename = fileName;
return Path.Combine(MafilesDirectory, fileName);
}
public static void BackupHandler(MobileDataExtended data)
private static string CreateMafileFileName(Mafile data, bool useAccountName)
{
if (Directory.Exists("mafiles_backup") == false)
{
Directory.CreateDirectory("mafiles_backup");
}
return useAccountName
? MafileNamingStrategy.Login.GetMafileName(data)
: MafileNamingStrategy.SteamId.GetMafileName(data);
}
public static string? TryGetMafilePath(Mafile data)
{
if (data.Filename == null) return null;
return Path.Combine(MafilesDirectory, data.Filename);
}
public static void WriteBackup(MobileDataExtended data)
{
var json = NebulaSerializer.SerializeMafile(data, null);
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"),
json);
WriteBackup(data.AccountName, json);
}
public static void BackupHandlerStr(string accountName, string data)
public static void WriteBackup(string accountName, string data)
{
if (Directory.Exists("mafiles_backup") == false)
Directory.CreateDirectory(DIR_BACKUP_MAFILES);
File.WriteAllText(Path.Combine(DIR_BACKUP_MAFILES, accountName + MafileNamingStrategy.DEF_EXTENSION), data);
}
public static async Task<MafileRenameResult> RenameMafiles(bool loginAsFileName, IProgress<double>? progress = null)
{
if (MaFiles.Count == 0) return new MafileRenameResult();
var now = DateTime.Now;
var backupFileName = $"rename_backup_{now:yyyy-MM-dd_HH-mm-ss}.zip";
var zipPath = Path.Combine(BackupMafilesDirectory, backupFileName);
await using (var zipStream = new FileStream(zipPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
Directory.CreateDirectory("mafiles_backup");
}
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false);
var files = Directory
.EnumerateFiles(MafilesDirectory, "*.*", SearchOption.TopDirectoryOnly)
.Where(f => Path.GetExtension(f).Equals(".mafile", StringComparison.OrdinalIgnoreCase))
.ToList();
File.WriteAllText(Path.Combine("mafiles_backup", accountName + ".mafile"),
data);
}
}
//TODO: Refactor
//TODO: use numeric orderer when .net 10 released
internal class MafileNameComparer : IComparer<string>
{
private const string MAF_64_START = "765";
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
public bool MafileNameMode { get; }
public MafileNameComparer(bool mafileNameMode)
{
MafileNameMode = mafileNameMode;
}
public int Compare(string? x, string? y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
var xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START);
var yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START);
if (xisSteamId ^ yisSteamId)
{
if (MafileNameMode)
var counter = 0;
foreach (var file in files)
{
return xisSteamId ? 1 : -1;
}
counter++;
var entry = archive.CreateEntry(Path.GetFileName(file), CompressionLevel.Optimal);
return yisSteamId ? 1 : -1;
await using var entryStream = entry.Open();
await using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096,
true);
await fileStream.CopyToAsync(entryStream);
if (counter % 5 == 0)
{
progress?.Report(counter / (double) files.Count);
await Task.Delay(10);
}
}
}
return DefaultComparer.Compare(x, y);
var mafiles = MaFiles.ToList();
var res = new MafileRenameResult
{
Total = mafiles.Count,
BackupFileName = backupFileName
};
var dic = new Dictionary<string, string>();
foreach (var mafile in mafiles)
{
try
{
var targetFileName = CreateMafileFileName(mafile, loginAsFileName);
if (mafile.Filename == targetFileName || mafile.Filename == null)
{
res.Renamed += 1;
continue;
}
var sourceFileName = mafile.Filename;
var fullSourcePath = Path.Combine(MafilesDirectory, sourceFileName);
var fullTargetPath = Path.Combine(MafilesDirectory, targetFileName);
if (!File.Exists(fullSourcePath))
{
Shell.Logger.Warn("Can't rename mafile {old} to {new} because source file not found",
mafile.Filename, targetFileName);
IncErrors();
continue;
}
if (File.Exists(fullTargetPath))
{
Shell.Logger.Warn("Can't rename mafile {old} to {new} because target file already exist",
mafile.Filename, targetFileName);
res.Conflict += 1;
continue;
}
File.Move(fullSourcePath, fullTargetPath);
dic[sourceFileName] = targetFileName;
res.Renamed += 1;
mafile.Filename = targetFileName;
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Error renaming mafile {file} {accountName}", mafile.Filename,
mafile.AccountName);
IncErrors();
}
}
MAACStorage.NotifyMafilesRenamed(dic);
return res;
void IncErrors()
{
res.Errors += 1;
}
}
public class MafileRenameResult
{
public int Total { get; set; }
public int Renamed { get; set; }
public int NotRenamed => Errors + Conflict;
public int Errors { get; set; }
public int Conflict { get; set; }
public string BackupFileName { get; set; }
}
}
+7 -2
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
@@ -10,7 +10,7 @@
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.7.3</AssemblyVersion>
<AssemblyVersion>1.8.0</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
@@ -65,4 +65,9 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Theme\Controls\" />
</ItemGroup>
</Project>
@@ -2,5 +2,6 @@
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=components/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=viewmodel_005Clinker_005Csteps/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
-11
View File
@@ -1,11 +0,0 @@
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
• Добавить полное шифрование мафайлов по аналогии с SDA
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
• Антик-окно как в MarketApp, возможность сразу открыть браузер напр. на CefSharp с залогиненым аккаунтом
• Стабильность авто-подтверждений, возможность задать пользователю порог времени когда льются ошибки и только тогда выключать авто-подтверждение
• Безопасное сохранение мафайлов через .tmp / .bak
• Создание механизма накопления ошибок, чтобы исправить Patch Tuesday, условно аккаунт может игнорировать ошибки 1-2 часа (настриавемо)
• Исправить "Перенос гуарда". Иногда не хочет принимать код / подтверждение
-220
View File
@@ -1,220 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace NebulaAuth.Theme;
public class FontScaleWindow : Window
{
// Using a DependencyProperty as the backing store for ScaleCoefficient. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleCoefficientProperty =
DependencyProperty.Register("ScaleCoefficient", typeof(double), typeof(FontScaleWindow),
new PropertyMetadata(1d));
public static readonly DependencyProperty DefaultFontSizeProperty =
DependencyProperty.Register("DefaultFontSize", typeof(double), typeof(FontScaleWindow),
new PropertyMetadata(20d));
private static readonly DependencyPropertyKey ParentScaleWindowKey
= DependencyProperty.RegisterAttachedReadOnly(
"ParentScaleWindow",
typeof(FontScaleWindow), typeof(FontScaleWindow),
new FrameworkPropertyMetadata(default(FontScaleWindow),
FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty ParentScaleWindowProperty
= ParentScaleWindowKey.DependencyProperty;
public static readonly DependencyProperty ResizeFontProperty = DependencyProperty.RegisterAttached(
"ResizeFont", typeof(bool), typeof(FontScaleWindow), new FrameworkPropertyMetadata(false, ResizeCallBack));
//<-------------------Window-------------------->
//<-------------------Scaling-------------------->
public static readonly DependencyProperty ScaleProperty = DependencyProperty.RegisterAttached(
"Scale", typeof(double), typeof(FontScaleWindow),
new FrameworkPropertyMetadata(0.9999d, FrameworkPropertyMetadataOptions.Inherits, ScalePropertyCallback));
//<-------------------Window-------------------->
public double DefaultFontSize
{
get => (double) GetValue(DefaultFontSizeProperty);
set => SetValue(DefaultFontSizeProperty, value);
}
public double ScaleCoefficient
{
get => (double) GetValue(ScaleCoefficientProperty);
set => SetValue(ScaleCoefficientProperty, value);
}
private readonly HashSet<FrameworkElement> _cachedObjects = new();
private readonly Func<double, double, double> _diagonal = (h, w) => Math.Sqrt(h * h + w * w);
private double _currentDiagonal;
private double _defaultDiagonal = 1;
private bool _loaded;
public FontScaleWindow()
{
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var w = (FontScaleWindow) sender;
w._defaultDiagonal = _diagonal(MinHeight, MinWidth);
w.SizeChanged += OnSizeChanged;
w.Loaded -= OnLoaded;
_loaded = true;
w.Width += 1;
w.Width -= 1;
}
private static FontScaleWindow? GetParentScaleWindow(DependencyObject element)
{
return (FontScaleWindow) element.GetValue(ParentScaleWindowProperty);
}
private static void SetParentScaleWindow(DependencyObject element, FontScaleWindow value)
{
element.SetValue(ParentScaleWindowKey, value);
}
public static void SetResizeFont(DependencyObject element, bool value)
{
element.SetValue(ResizeFontProperty, value);
}
public static bool GetResizeFont(DependencyObject element)
{
return (bool) element.GetValue(ResizeFontProperty);
}
private static bool SetWindow(FrameworkElement el)
{
var w = GetWindow(el);
if (w is not FontScaleWindow fsWindow) return false;
SetParentScaleWindow(el, fsWindow);
if (fsWindow._cachedObjects.Contains(el)) return true;
fsWindow._cachedObjects.Add(el);
el.Unloaded += ObjOnUnloaded;
return true;
}
private static void ObjOnLoaded(object sender, RoutedEventArgs e)
{
var el = (FrameworkElement) sender;
if (GetParentScaleWindow(el) == null)
{
if (!SetWindow(el))
{
el.Loaded -= ObjOnLoaded;
return;
}
}
ResizeCallBack(sender, new DependencyPropertyChangedEventArgs());
}
private static void ResizeCallBack(object? sender, DependencyPropertyChangedEventArgs eventArgs)
{
if (sender is not FrameworkElement obj) return;
var window = GetParentScaleWindow(obj);
if (window != null && !window._cachedObjects.Contains(obj))
{
if (!SetWindow(obj)) return;
}
else if (window == null)
{
obj.Loaded += ObjOnLoaded;
}
if (window is not {_loaded: true}) return;
CalculateFontSizeResized(obj);
}
private static void ObjOnUnloaded(object sender, RoutedEventArgs e)
{
var obj = (FrameworkElement) sender;
var w = GetParentScaleWindow(obj)!;
w._cachedObjects.Remove(obj);
obj.Unloaded -= ObjOnUnloaded;
obj.Loaded -= ObjOnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
_currentDiagonal = _diagonal(e.NewSize.Width, e.NewSize.Height);
foreach (var cached in _cachedObjects)
{
CalculateFontSizeResized(cached);
}
}
public static void SetScale(DependencyObject element, double value)
{
element.SetValue(ScaleProperty, value);
}
public static double GetScale(DependencyObject element)
{
return (double) element.GetValue(ScaleProperty);
}
private static void ScalePropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = GetParentScaleWindow(d);
if (window == null)
{
var w = GetWindow(d);
if (w is FontScaleWindow fsWindow)
{
SetParentScaleWindow(d, fsWindow);
}
}
ResizeElement(d);
}
public static void ResizeElement(DependencyObject d)
{
var set = d.ReadLocalValue(ScaleProperty) != DependencyProperty.UnsetValue;
if (set)
{
if (GetResizeFont(d))
{
CalculateFontSizeResized(d);
}
else
{
CalculateFontSize(d);
}
}
}
public static void CalculateFontSize(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var fontSize = GetScale(d) * window._defaultDiagonal;
d.SetValue(FontSizeProperty, fontSize);
}
public static void CalculateFontSizeResized(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var windowsScale = Math.Pow(window._currentDiagonal / window._defaultDiagonal, window.ScaleCoefficient);
var elScale = GetScale(d);
var fontSize = window.DefaultFontSize * elScale * windowsScale;
d.SetValue(FontSizeProperty, fontSize);
}
}
@@ -2,9 +2,6 @@
namespace NebulaAuth.Theme.WindowStyle;
/// <summary>
/// Логика взаимодействия для WindowStyle.xaml
/// </summary>
partial class WindowStyle
{
public WindowStyle()
+17 -35
View File
@@ -1,6 +1,6 @@
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Forms;
using NebulaAuth.Core;
using NebulaAuth.Model;
@@ -10,24 +10,15 @@ public class ClipboardHelper
{
public static bool Set(string text)
{
var i = 0;
while (i < 20)
try
{
try
{
Clipboard.SetText(text);
return true;
}
catch (Exception ex)
{
if (i == 19)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
}
i++;
Clipboard.SetText(text);
return true;
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
return false;
@@ -35,24 +26,15 @@ public class ClipboardHelper
public static bool SetFiles(StringCollection files)
{
var i = 0;
while (i < 20)
try
{
try
{
Clipboard.SetFileDropList(files);
return true;
}
catch (Exception ex)
{
if (i == 19)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
}
i++;
Clipboard.SetFileDropList(files);
return true;
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
return false;
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NebulaAuth.Core;
@@ -6,6 +7,7 @@ using NebulaAuth.Model;
using SteamLib.Exceptions;
using SteamLib.Exceptions.Authorization;
using SteamLib.Exceptions.General;
using SteamLib.ProtoCore.Enums;
using SteamLibForked.Exceptions.Authorization;
namespace NebulaAuth.Utility;
@@ -94,6 +96,18 @@ public static class ExceptionHandler
return "LoginException".GetCodeBehindLocalization() + ": " +
ErrorTranslatorHelper.TranslateLoginError(e.Error);
}
case UnsupportedAuthTypeException e:
{
var requestedType = e.AllowedGuardTypes.First();
var msgType = requestedType switch
{
EAuthSessionGuardType.DeviceCode or EAuthSessionGuardType.DeviceConfirmation => "Guard",
EAuthSessionGuardType.EmailCode => "Mail",
_ => "Unknown"
};
return GetCodeBehindLocalization("UnsupportedAuthTypeException", msgType);
}
case not null when handleAllExceptions:
{
return "UnknownException".GetCodeBehindLocalization() + exception.Message;
@@ -112,4 +126,11 @@ public static class ExceptionHandler
{
return LocManager.GetCodeBehindOrDefault(key, EXCEPTION_HANDLER_LOC_PATH, key);
}
private static string GetCodeBehindLocalization(params string[] path)
{
var def = path.Length == 0 ? "" : path.Last();
var newArr = path.Prepend(EXCEPTION_HANDLER_LOC_PATH).ToArray();
return LocManager.GetCodeBehindOrDefault(def, newArr);
}
}
@@ -0,0 +1,22 @@
using System.IO;
using System.Linq;
namespace NebulaAuth.Utility;
public static class FileNameValidator
{
public static bool IsValidFileName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return false;
var invalidChars = Path.GetInvalidFileNameChars();
return !name.Any(c => invalidChars.Contains(c));
}
public static string GetInvalidChars(string name)
{
var invalidChars = Path.GetInvalidFileNameChars();
return new string(name.Where(c => invalidChars.Contains(c)).Distinct().ToArray());
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Globalization;
using System.Linq;
using NebulaAuth.Core;
namespace NebulaAuth.Utility;
public static class LanguageUtility
{
public static LocalizationLanguage DetectPreferredLanguage()
{
var userCulture = CultureInfo.CurrentUICulture;
var userLang = userCulture.TwoLetterISOLanguageName;
var userRegion = userCulture.Name;
switch (userLang)
{
case "ru": return LocalizationLanguage.Russian;
case "uk": return LocalizationLanguage.Ukrainian;
case "en": return LocalizationLanguage.English;
}
if (userRegion.EndsWith("UA", StringComparison.OrdinalIgnoreCase))
return LocalizationLanguage.Ukrainian;
string[] cisRegions =
[
"RU", "BY", "KZ", "KG", "TJ", "TM", "UZ", "AM", "AZ", "GE", "MD"
];
if (cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase)))
return LocalizationLanguage.Russian;
return LocalizationLanguage.English;
}
}
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:confirmations="clr-namespace:SteamLib.SteamMobile.Confirmations;assembly=SteamLibForked"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
@@ -110,7 +109,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart"
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="CartArrowUp"
Margin="0,0,10,0" />
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36"
HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}"
@@ -119,7 +118,7 @@
Source="{Binding ItemImageUri}" />
</Border>
<Grid Grid.Column="2">
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True"
<TextBlock VerticalAlignment="Center" x:Name="ItemName"
TextWrapping="WrapWithOverflow" Text="{Binding ItemName}" />
<TextBlock Foreground="LightGray" Text="{Binding PriceString}">
@@ -184,7 +183,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus"
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="CartArrowUp"
Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Grid.Column="1">
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}" />
@@ -4,42 +4,69 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
Height="Auto" Width="Auto" MaxWidth="500"
Background="{DynamicResource WindowBackground}">
<Grid Margin="15">
<Grid MinHeight="140" MinWidth="350">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock theme:FontScaleWindow.Scale="0.8"
theme:FontScaleWindow.ResizeFont="True"
HorizontalAlignment="Center" Margin="10,10,10,15"
Text="Подтвердите действие"
x:Name="ConfirmTextBlock"
TextWrapping="WrapWithOverflow" />
<Grid Grid.Row="1" Margin="10,0,10,5">
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button DockPanel.Dock="Left"
<StackPanel Orientation="Horizontal">
<!--<materialDesign:PackIcon Kind="WarningCircleOutline" Width="20" Height="20" Margin="0,0,5,0"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryContentBrush}" />-->
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr ConfirmCancelDialog.Title}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" Opacity="0.7" />
<Grid Grid.Row="2" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<nebulaAuth:HintBox FontSize="14" x:Name="ConfirmHint" Text="Confirm?" Margin="10" />
<Grid Grid.Row="1" Margin="10,0,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="130"
HorizontalAlignment="Left"
Margin="2,0,4,0"
Style="{StaticResource MaterialDesignRaisedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource True}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
ОК
</Button>
<Button Grid.Column="1" DockPanel.Dock="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource False}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
Отмена
</Button>
Content="{Tr Common.OK}"
IsDefault="True" />
<Button Grid.Column="1"
HorizontalAlignment="Right"
Width="130"
Margin="0,0,2,0"
IsCancel="True"
Style="{StaticResource MaterialDesignOutlinedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource False}"
Content="{Tr Common.Cancel}" />
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -1,8 +1,5 @@
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog
{
public ConfirmCancelDialog()
@@ -13,6 +10,6 @@ public partial class ConfirmCancelDialog
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
ConfirmHint.Text = msg;
}
}
@@ -45,7 +45,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
<TextBox TabIndex="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.HasClearButton="True"
Padding="10"
@@ -1,8 +1,5 @@
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog
{
public LoginAgainDialog()
@@ -49,7 +49,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
<TextBox TabIndex="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.HasClearButton="True"
Padding="10"
@@ -1,8 +1,5 @@
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
{
public LoginAgainOnImportDialog()
@@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance other:LoginAgainVM}">
@@ -29,7 +28,7 @@
Foreground="{DynamicResource PrimaryContentBrush}" />
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr SetEncryptedPasswordDialog.Title}" />
VerticalAlignment="Center" FontSize="18" Text="{Tr SetEncryptionPasswordDialog.Title}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
@@ -40,12 +39,13 @@
</Grid>
<Separator Grid.Row="1" Opacity="0.7" />
<TextBlock Grid.Row="2" IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal"
Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8"
theme:FontScaleWindow.ResizeFont="True" Margin="10" HorizontalAlignment="Left" />
<PasswordBox FontSize="16"
FontSize="16"
Text="{Tr SetEncryptionPasswordDialog.DialogText}"
Margin="10" HorizontalAlignment="Left" />
<PasswordBox TabIndex="0" FontSize="16" x:Name="PasswordBox"
materialDesign:PasswordBoxAssist.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
Margin="10" Grid.Row="3" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
materialDesign:HintAssist.Hint="{Tr SetEncryptionPasswordDialog.Password}" />
<Grid Grid.Row="4" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
@@ -53,11 +53,11 @@
</Grid.ColumnDefinitions>
<Button IsDefault="True" Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}" />
CommandParameter="{StaticResource True}" Content="{Tr SetEncryptionPasswordDialog.Ok}" />
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5"
Style="{StaticResource MaterialDesignOutlinedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource False}" Content="{Tr SetEncryptedPasswordDialog.Cancel}" />
CommandParameter="{StaticResource False}" Content="{Tr SetEncryptionPasswordDialog.Cancel}" />
</Grid>
</Grid>
</UserControl>
@@ -1,8 +1,5 @@
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
/// </summary>
public partial class SetCryptPasswordDialog
{
public SetCryptPasswordDialog()
@@ -0,0 +1,70 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.TextFieldDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Height="Auto" Width="Auto" MaxWidth="500"
Background="{DynamicResource WindowBackground}">
<Grid MinHeight="140" MinWidth="350">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<!--<materialDesign:PackIcon Kind="WarningCircleOutline" Width="20" Height="20" Margin="0,0,5,0"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryContentBrush}" />-->
<TextBlock x:Name="TitleTextBlock" FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr TextFieldDialog.Title}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" CommandParameter="{x:Null}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" Opacity="0.7" />
<Grid Grid.Row="2" Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox x:Name="TextField" Margin="10" materialDesign:HintAssist.Hint="" />
<Grid Grid.Row="1" Margin="10,0,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="130"
HorizontalAlignment="Left"
Margin="2,0,4,0"
Style="{StaticResource MaterialDesignRaisedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{Binding Text, ElementName=TextField}"
Content="{Tr Common.OK}"
IsDefault="True" />
<Button Grid.Column="1"
HorizontalAlignment="Right"
Width="130"
Margin="0,0,2,0"
Style="{StaticResource MaterialDesignOutlinedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
IsCancel="True"
CommandParameter="{x:Null}"
Content="{Tr Common.Cancel}" />
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,22 @@
using MaterialDesignThemes.Wpf;
namespace NebulaAuth.View.Dialogs;
public partial class TextFieldDialog
{
public TextFieldDialog()
{
InitializeComponent();
}
public TextFieldDialog(string? title, string? msg)
{
InitializeComponent();
if (!string.IsNullOrWhiteSpace(title))
TitleTextBlock.Text = title;
if (!string.IsNullOrEmpty(msg))
HintAssist.SetHint(TextField, msg);
}
}
@@ -3,9 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
mc:Ignorable="d"
theme:FontScaleWindow.ResizeFont="True" theme:FontScaleWindow.Scale="1"
Foreground="WhiteSmoke"
Background="{DynamicResource WindowBackground}">
<Grid Margin="20">
@@ -19,7 +17,8 @@
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True" />
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center" />
<TextBlock FontSize="17" Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}"
VerticalAlignment="Center" />
</Grid>
<Grid x:Name="CaptchaGrid" Margin="0,25,0,0" Visibility="Collapsed" Grid.Row="1">
<Grid.RowDefinitions>
@@ -28,7 +27,8 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="CaptchaImage" Stretch="Uniform" />
<TextBox Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" x:Name="CaptchaTB" />
<TextBox TabIndex="0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}"
x:Name="CaptchaTB" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition />
@@ -3,9 +3,6 @@ using System.Windows;
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для WaitLoginDialog.xaml
/// </summary>
public partial class WaitLoginDialog
{
private TaskCompletionSource<string> _tcs = new();
@@ -11,6 +11,7 @@
<TextBlock Margin="5,0,0,0" Text="{Tr LinkerDialog.Authorization}" />
</StackPanel>
<TextBox
TabIndex="0"
Padding="8"
FontSize="14"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
@@ -22,6 +23,7 @@
Margin="0,10,0,0" />
<TextBox
Padding="8"
TabIndex="1"
FontSize="14"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
+10 -36
View File
@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:linker="clr-namespace:NebulaAuth.ViewModel.Linker"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
FontFamily="{materialDesign:MaterialDesignFont}"
@@ -29,8 +30,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="materialDesign:PackIcon">
@@ -81,45 +80,20 @@
</Button>
</Grid>
<Separator Background="DarkGray" Opacity="0.4" Grid.Row="1" />
<Border Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="2" Margin="10,10,10,0" Padding="5"
BorderThickness="1"
CornerRadius="12">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="InfoCircleOutline" Foreground="{DynamicResource InfoBrush}" Height="22"
Width="22" VerticalAlignment="Center" Margin="5,0,0,0" />
<TextBlock VerticalAlignment="Center" MaxWidth="330" FontSize="14" TextWrapping="WrapWithOverflow"
Margin="10,10,10,10" Text="{Binding Tip}" />
</StackPanel>
</Border>
<nebulaAuth:HintBox FontSize="14"
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="2" Margin="10,10,10,0"
Text="{Binding Tip}" MaxWidth="330" />
<ContentControl
Grid.Row="3" Margin="20"
d:DataContext="{d:DesignInstance Type=linker:DesignLinkAccountAuthStepVM, IsDesignTimeCreatable=True}"
Content="{Binding CurrentStep}" />
<Border Visibility="{Binding Error, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="4" Margin="10,0,10,10" Padding="5"
BorderThickness="1"
CornerRadius="12">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="ErrorOutline" Foreground="{DynamicResource ErrorBrush}" Height="22"
Width="22" VerticalAlignment="Center" Margin="5,0,0,0" />
<TextBlock VerticalAlignment="Center" MaxWidth="330" FontSize="14" TextWrapping="WrapWithOverflow"
Margin="10,10,10,10" Text="{Binding Error}" />
</StackPanel>
</Border>
<nebulaAuth:HintBox FontSize="14"
Visibility="{Binding Error, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="4" Margin="10,0,10,10"
Severity="Error"
Text="{Binding Error}" />
</Grid>
</UserControl>
-3
View File
@@ -1,8 +1,5 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для LinkerView.xaml
/// </summary>
public partial class LinkerView
{
public LinkerView()
-3
View File
@@ -4,9 +4,6 @@ using System.Windows.Controls;
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для LinksView.xaml
/// </summary>
public partial class LinksView : UserControl
{
public LinksView()
@@ -13,6 +13,7 @@
</StackPanel>
<TextBox
TabIndex="0"
Padding="8"
FontSize="14"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
@@ -25,6 +26,7 @@
<TextBox
Padding="8"
FontSize="14"
TabIndex="1"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
materialDesign:HintAssist.Hint="{Tr LinkerDialog.Password}"
+9 -35
View File
@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mafileMover="clr-namespace:NebulaAuth.ViewModel.MafileMover"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
FontFamily="{materialDesign:MaterialDesignFont}"
@@ -76,43 +77,16 @@
</Button>
</Grid>
<Separator Background="DarkGray" Opacity="0.4" Grid.Row="1" />
<Border Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="2" Margin="10,10,10,0" Padding="5"
BorderThickness="1"
CornerRadius="12">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="InfoCircleOutline" Foreground="{DynamicResource InfoBrush}" Height="22"
Width="22" VerticalAlignment="Center" Margin="5,0,0,0" />
<TextBlock VerticalAlignment="Center" MaxWidth="330" FontSize="14" TextWrapping="WrapWithOverflow"
Margin="10,10,10,10" Text="{Binding Tip}" />
</StackPanel>
</Border>
<nebulaAuth:HintBox FontSize="14"
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="2" Margin="10,10,10,0"
Text="{Binding Tip}" />
<ContentControl
Grid.Row="3" Margin="20" Content="{Binding CurrentStep}" />
<Border Visibility="{Binding Error, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="4" Margin="10,0,10,10" Padding="5"
BorderThickness="1"
CornerRadius="12">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="ErrorOutline" Foreground="{DynamicResource ErrorBrush}" Height="22"
Width="22" VerticalAlignment="Center" Margin="5,0,0,0" />
<TextBlock VerticalAlignment="Center" MaxWidth="330" FontSize="14" TextWrapping="WrapWithOverflow"
Margin="10,10,10,10" Text="{Binding Error}" />
</StackPanel>
</Border>
<nebulaAuth:HintBox FontSize="14"
Visibility="{Binding Error, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="4" Margin="10,0,10,10"
Text="{Binding Error}" />
</Grid>
</UserControl>
@@ -2,9 +2,6 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для MafileMoverView.xaml
/// </summary>
public partial class MafileMoverView : UserControl
{
public MafileMoverView()
+101 -35
View File
@@ -5,14 +5,19 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="400"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
MinWidth="400"
MaxHeight="550"
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
Background="Transparent">
d:Background="#141119"
d:Foreground="White"
FontFamily="{md:MaterialDesignFont}"
MinHeight="700"
MinWidth="600"
MaxHeight="700"
MaxWidth="600"
Background="Transparent"
FontSize="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -41,21 +46,42 @@
</Grid>
<Separator Grid.Row="1" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch"
Margin="15" FontSize="16">
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}" />
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='-', Mode=OneWay}" />
<Run
Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}" />
</TextBlock>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch">
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}" />
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='-', Mode=OneWay}" />
<Run>
<Run.Text>
<MultiBinding Mode="OneWay"
Converter="{StaticResource ProxyDataTextMultiConverter}">
<Binding Path="DefaultProxy.Value" />
<Binding Path="DataContext.DisplayProtocol"
RelativeSource="{RelativeSource AncestorType=UserControl}" />
<Binding Path="DataContext.DisplayCredentials"
RelativeSource="{RelativeSource AncestorType=UserControl}" />
</MultiBinding>
</Run.Text>
</Run>
</TextBlock>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
</Button>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
</Button>
</Grid>
<StackPanel Grid.Row="1" Margin="15,0,15,15">
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyProtocol}" IsChecked="{Binding DisplayProtocol}" />
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyCredentials}"
IsChecked="{Binding DisplayCredentials}" />
</StackPanel>
</Grid>
<md:Card Grid.Row="3" Margin="10">
<ListBox VirtualizingStackPanel.VirtualizationMode="Recycling" FontSize="14"
@@ -77,6 +103,9 @@
<MenuItem Header="{Tr MainWindow.ContextMenus.Proxy.CopyAddress}"
Command="{Binding PlacementTarget.Tag.CopyProxyAddressCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}" />
<MenuItem Header="{Tr Common.Delete}"
Command="{Binding PlacementTarget.Tag.RemoveProxyCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}" />
</ContextMenu>
</Setter.Value>
</Setter>
@@ -95,8 +124,18 @@
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">
<Run Text="{Binding Key, Mode=OneWay}" /><Run Text=": " />
<Run
Text="{Binding Value, Mode=OneWay, Converter={StaticResource ProxyDataTextConverter}}" />
<Run>
<Run.Text>
<MultiBinding Mode="OneWay"
Converter="{StaticResource ProxyDataTextMultiConverter}">
<Binding Path="Value" />
<Binding Path="DataContext.DisplayProtocol"
RelativeSource="{RelativeSource AncestorType=UserControl}" />
<Binding Path="DataContext.DisplayCredentials"
RelativeSource="{RelativeSource AncestorType=UserControl}" />
</MultiBinding>
</Run.Text>
</Run>
</TextBlock>
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24"
@@ -114,20 +153,47 @@
</ListBox>
</md:Card>
<Grid Grid.Row="4" Margin="5,0,15,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True"
AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}"
Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}" />
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add" />
</Button>
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
<md:PackIcon Kind="Trash" />
</Button>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<nebulaAuth:HintBox
Visibility="{Binding ErrorText, Converter={StaticResource NullableToVisibilityConverter}}"
Margin="15,0,15,0"
Grid.Row="0"
Severity="Error"
FontSize="14"
Text="{Binding ErrorText}"
CloseCommand="{Binding ClearErrorCommand}"
ShowCloseButton="True" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding AddProxyField}"
KeyDown="ProxyInput_KeyDown"
FontSize="12"
md:TextFieldAssist.HasClearButton="True"
AcceptsReturn="True"
MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}"
Margin="15"
Height="90"
md:HintAssist.IsFloating="False"
md:HintAssist.Hint="IP:PORT&#x0a;IP:PORT:USER:PASS&#x0a;PROTOCOL://IP:PORT:USER:PASS&#x0a;IP:PORT:USER:PASS{ID}
" />
<Button x:Name="AddProxyBtn" ToolTip="CTRL+ENTER" IsDefault="True" Grid.Column="1" Height="90"
Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add" />
</Button>
<Button Grid.Column="2" Height="90" Command="{Binding RemoveProxyCommand}" CommandParameter="{x:Null}">
<md:PackIcon Kind="Trash" />
</Button>
</Grid>
</Grid>
+12 -4
View File
@@ -1,12 +1,20 @@
namespace NebulaAuth.View;
using System.Windows.Controls;
using System.Windows.Input;
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для ProxyManagerView.xaml
/// </summary>
public partial class ProxyManagerView
{
public ProxyManagerView()
{
InitializeComponent();
}
private void ProxyInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter || !Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) return;
var tb = sender as TextBox;
tb?.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
AddProxyBtn.Command.Execute(null);
}
}
@@ -0,0 +1,152 @@
<UserControl x:Class="NebulaAuth.View.SetAccountPasswordsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="400"
d:DataContext="{d:DesignInstance other:SetAccountPasswordsVM}"
d:Background="#141119"
d:Foreground="White"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
MaxHeight="500"
MinWidth="400"
MaxWidth="400"
Background="Transparent"
FontSize="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- ===== HEADER ===== -->
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="ShieldKey" Width="20" Height="20"
Margin="0,0,5,0" VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryContentBrush}" />
<TextBlock Text="{Tr SetAccountPasswordsView.Title}"
FontSize="18"
VerticalAlignment="Center"
Foreground="{DynamicResource BaseContentBrush}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30"
Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right"
Command="{x:Static md:DialogHost.CloseDialogCommand}"
IsCancel="True">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" />
<!-- ===== ENCRYPTION PASSWORD SECTION ===== -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<md:Card Margin="15,15,15,15" BorderThickness="1" BorderBrush="Gray" Padding="12">
<StackPanel>
<TextBlock Text="{Tr SetAccountPasswordsView.EncryptionPassword}" FontSize="16"
Margin="0,0,0,5"
Foreground="{DynamicResource PrimaryContentBrush}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
md:HintAssist.Hint="{Tr SetAccountPasswordsView.EnterPassword}"
md:HintAssist.IsFloating="False"
Margin="0,0,10,0"
Text="{Binding EncryptionPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1" Command="{Binding SetEncryptionPasswordCommand}">
<md:PackIcon Kind="ContentSave" />
</Button>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<StackPanel.Resources>
<x:Array x:Key="BoolToIcon" Type="{x:Type md:PackIconKind}">
<md:PackIconKind>CheckBox</md:PackIconKind>
<md:PackIconKind>CloseBox</md:PackIconKind>
</x:Array>
<x:Array x:Key="BoolToColor" Type="{x:Type SolidColorBrush}">
<SolidColorBrush Color="{DynamicResource SuccessColor}" />
<SolidColorBrush Color="{DynamicResource ErrorColor}" />
</x:Array>
</StackPanel.Resources>
<TextBlock Text="{Tr SetAccountPasswordsView.Status}" VerticalAlignment="Center" />
<md:PackIcon Width="20" Height="20"
VerticalAlignment="Center"
Foreground="{Binding IsEncryptionPasswordSet, Converter={StaticResource BoolToValueConverter}, ConverterParameter={StaticResource BoolToColor}}"
Kind="{Binding IsEncryptionPasswordSet, Converter={StaticResource BoolToValueConverter}, ConverterParameter={StaticResource BoolToIcon}}" />
</StackPanel>
</StackPanel>
</md:Card>
</Grid>
<!-- ===== ACCOUNTS INPUT ===== -->
<TextBox Grid.Row="3"
Margin="15,0,15,10"
Text="{Binding AccountsPasswords, UpdateSourceTrigger=PropertyChanged}"
FontSize="13"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
md:TextFieldAssist.HasClearButton="True"
md:HintAssist.IsFloating="False"
md:HintAssist.Hint="{Tr SetAccountPasswordsView.InputFormat}"
md:TextFieldAssist.TextBoxIsMultiLine="True"
md:TextFieldAssist.TextBoxViewVerticalAlignment="Top"
VerticalContentAlignment="Top" />
<!-- ===== INFO TEXT ===== -->
<TextBlock Grid.Row="4"
Margin="20,0,20,15"
TextWrapping="Wrap"
FontSize="12"
Foreground="{DynamicResource BaseContentBrush}"
Text="{Tr SetAccountPasswordsView.EncryptionHint}" />
<Button Grid.Row="5"
Margin="10,0,0,10"
Width="120"
Content="{Tr Common.Save}"
Command="{Binding SetPasswordsCommand}"
Style="{StaticResource MaterialDesignOutlinedButton}" Cursor="Hand" />
<!-- ===== HINT / RESULT BOX ===== -->
<nebulaAuth:HintBox Grid.Row="6"
Margin="15,0,15,10"
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
CloseCommand="{Binding ClearTipCommand}"
Text="{Binding Tip}"
FontSize="13"
Severity="Info"
ShowCloseButton="True" />
</Grid>
</UserControl>
@@ -0,0 +1,9 @@
namespace NebulaAuth.View;
public partial class SetAccountPasswordsView
{
public SetAccountPasswordsView()
{
InitializeComponent();
}
}
+78 -40
View File
@@ -5,14 +5,16 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DesignHeight="650"
FontFamily="{materialDesign:MaterialDesignFont}"
MinHeight="600"
MinWidth="400"
MaxWidth="400"
MinHeight="400"
MinWidth="480"
MaxWidth="480"
d:DataContext="{d:DesignInstance other:SettingsVM}"
Background="Transparent">
Background="Transparent"
FontSize="15">
<Grid>
<Grid>
<Grid.RowDefinitions>
@@ -37,6 +39,7 @@
<Button Grid.Column="1" Width="30" Height="30"
Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
IsEnabled="{Binding ApplyRenameSettingCommand.IsRunning, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}"
IsCancel="True">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
@@ -46,25 +49,44 @@
<TabControl materialDesign:ColorZoneAssist.Background="{DynamicResource Base100Brush}"
Style="{StaticResource MaterialDesignUniformTabControl}" Grid.Row="2">
<TabItem Header="{Tr SettingsDialog.MainSettings}">
<StackPanel Width="340" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<StackPanel Width="400" Margin="10,18,10,40" Orientation="Vertical" HorizontalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,15,0,0"
FontSize="16"
ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding Language}"
materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
<CheckBox Margin="0,15,0,0" FontSize="16" IsChecked="{Binding HideToTray}"
<CheckBox Margin="0,18,0,0" IsChecked="{Binding HideToTray}"
Content="{Tr SettingsDialog.MinimizeToTray}" />
<CheckBox Margin="0,15,0,0" FontSize="16" IsChecked="{Binding UseIcon}"
<CheckBox Margin="0,18,0,0" IsChecked="{Binding LegacyMode}"
Content="{Tr SettingsDialog.LegacyMafileMode}"
ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" />
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" BorderBrush="AliceBlue"
Margin="0,18,0,0"
BorderThickness="2"
IsChecked="{Binding IgnorePatchTuesdayErrors}"
ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
<TextBlock TextWrapping="WrapWithOverflow"
Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
</CheckBox>
<!--<CheckBox Margin="0,12,0,0" IsChecked="{Binding UseAccountNameAsMafileName}">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
</CheckBox>-->
<CheckBox Margin="0,20,0,0" IsChecked="{Binding UseIcon}"
Content="{Tr SettingsDialog.UseIndicator}"
ToolTip="{Tr SettingsDialog.UseIndicatorHint}" />
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
<Grid Margin="0,20,0,10">
<materialDesign:ColorPicker Margin="-5,0,-5,0" IsEnabled="{Binding UseIcon}"
Color="{Binding IconColor, Delay=50}" />
<Grid Margin="0,10,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}"
Height="Auto" VerticalAlignment="Center" FontSize="16"
Height="Auto" VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
@@ -73,46 +95,60 @@
<materialDesign:PackIcon Kind="ContentSave" />
</Button>
</Grid>
<CheckBox Margin="0,15,0,0" FontSize="16" IsChecked="{Binding LegacyMode}"
Content="{Tr SettingsDialog.LegacyMafileMode}"
ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" />
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
<CheckBox Margin="0,15,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" BorderBrush="AliceBlue"
BorderThickness="2" IsChecked="{Binding IgnorePatchTuesdayErrors}" Margin="0,15,0,0"
FontSize="16"
ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
<TextBlock TextWrapping="WrapWithOverflow"
Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
</CheckBox>
<ComboBox materialDesign:HintAssist.Hint="{Tr SettingsDialog.MafileNamingMode}"
Margin="0,25,0,0"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
SelectedItem="{Binding UseAccountNameAsMafileNamePreview}">
<ComboBox.ItemsSource>
<x:Array Type="{x:Type sys:Boolean}"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Boolean>True</sys:Boolean>
<sys:Boolean>False</sys:Boolean>
</x:Array>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource BoolToMafileNamingConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Margin="0,10,0,0"
materialDesign:ButtonProgressAssist.Maximum="1"
materialDesign:ButtonProgressAssist.Value="{Binding RenameMafilesProgress}"
materialDesign:ButtonProgressAssist.IsIndicatorVisible="{Binding ApplyRenameSettingCommand.IsRunning}"
Style="{StaticResource MaterialDesignOutlinedButton}"
Command="{Binding ApplyRenameSettingCommand}"
Content="{Tr SettingsDialog.ApplyNamingMode}" />
<nebulaAuth:HintBox Margin="0,10,0,0" Text="{Binding RenameResultText}"
FontSize="14"
Visibility="{Binding RenameResultText, Converter={StaticResource NullableToVisibilityConverter}, Mode=OneWay}" />
</StackPanel>
</TabItem>
<TabItem IsEnabled="True" Header="{Tr SettingsDialog.ThemeSettings}">
<StackPanel Width="340" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<TabItem TextElement.FontSize="13" IsEnabled="True" Header="{Tr SettingsDialog.ThemeSettings}">
<StackPanel Width="340" Margin="10,20,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}"
Padding="8"
Margin="0,15,0,0"
FontSize="16"
FontSize="15"
ItemsSource="{Binding ThemeTypes}" DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding ThemeType}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.Theme.CurrentTheme}" />
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}"
Padding="8"
Margin="0,15,0,0"
FontSize="15"
ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding BackgroundMode}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
<!-- Theme input -->
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}"
Padding="8"
Margin="0,15,0,0"
FontSize="16"
ItemsSource="{Binding ThemeTypes}" DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding ThemeType}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.Theme.CurrentTheme}" />
<TextBlock Text="{Tr SettingsDialog.Theme.BackgroundBlur}" Margin="0,15,0,0" />
<Slider materialDesign:SliderAssist.HideActiveTrack="True"
TickFrequency="5"
Minimum="0" Maximum="100" Value="{Binding BackgroundBlur, Mode=TwoWay}"
materialDesign:HintAssist.Hint="Blur Amount"
@@ -156,14 +192,16 @@
<CheckBox Margin="0,15,0,0" Content="{Tr SettingsDialog.Theme.DialogBlur}"
IsChecked="{Binding ApplyBlurBackground}" />
IsChecked="{Binding ApplyBlurBackground}"
FontSize="15" />
<CheckBox Margin="0,15,0,0" Content="{Tr SettingsDialog.Theme.RippleDisabled}"
FontSize="15"
IsChecked="{Binding RippleDisabled}" />
<Button Margin="0,15,0,0" Command="{Binding ResetThemeDefaultsCommand}"
Content="{Tr SettingsDialog.Theme.Reset}" />
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Grid>
</UserControl>
-3
View File
@@ -5,9 +5,6 @@ using MaterialDesignThemes.Wpf;
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView
{
private readonly DialogHost? _dialogHost;
-3
View File
@@ -1,8 +1,5 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView
{
public UpdaterView()
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
@@ -177,7 +176,7 @@ public partial class LinkAccountVM : ObservableObject, ISmsCodeProvider, IPhoneN
var linkOptions = new LinkOptions(_client, StaticLoginConsumer.Instance, this,
this, this, Storage.BackupHandler, Logger2);
this, this, Storage.WriteBackup, Logger2);
var linker = new SteamAuthenticatorLinker(linkOptions);
MobileDataExtended result;
try
@@ -226,10 +225,93 @@ public partial class LinkAccountVM : ObservableObject, ISmsCodeProvider, IPhoneN
}
Storage.SaveMafile(mafile);
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
await Done(mafile.RevocationCode ?? string.Empty, mafile.SteamId.Steam64.ToString());
await Done(mafile.RevocationCode ?? string.Empty, mafile.SteamId.Steam64.ToString(), login);
}
private void SetCurrentStep(LinkAccountStepVM step)
{
Dispatcher.CurrentDispatcher.Invoke(() =>
{
CurrentStep = step;
Tip = CurrentStep.Tip;
});
}
[RelayCommand]
private void OpenTroubleshooting()
{
const string troubleshootingURI =
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
{
UseShellExecute = true
});
}
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
}
private static string GetLocalizationCommon(string key)
{
return LocManager.GetCommonOrDefault(key, key);
}
public void Dispose()
{
_client.Dispose();
_handler.Dispose();
try
{
_cts.Cancel();
}
catch
{
//Ignored, may be cancelled or disposed
}
_cts.Dispose();
}
// @formatter:off
#region Step 2: Email Code
public bool IsSupportedGuardType(ILoginConsumer consumer, EAuthSessionGuardType type)
{
return type == EAuthSessionGuardType.EmailCode;
}
// Step 2: Email Code
public async Task UpdateAuthSession(HttpClient authClient, ILoginConsumer loginConsumer,
UpdateAuthSessionModel model,
CancellationToken cancellationToken = default)
{
while (true)
{
try
{
var step = new LinkAccountEmailAuthStepVM();
SetCurrentStep(step);
var res = await step.GetResultAsync();
var req = AuthRequestHelper.CreateEmailCodeRequest(res, model.ClientId, model.SteamId);
await AuthenticationServiceApi.UpdateAuthSessionWithSteamGuardCode(authClient, req, cancellationToken);
Error = null;
break;
}
catch (SteamStatusCodeException ex)
when (ex.StatusCode.Equals(SteamStatusCode.InvalidLoginAuthCode))
{
Error = ExceptionHandler.GetExceptionString(ex);
}
}
}
#endregion
#region Step 3: Phone Number
// Step 3: Phone number
@@ -280,95 +362,14 @@ public partial class LinkAccountVM : ObservableObject, ISmsCodeProvider, IPhoneN
#region Step 7: Done
public async Task Done(string rCode, string steamId)
public async Task Done(string rCode, string steamId, string login)
{
var step = new LinkAccountDoneStepVM(rCode, steamId);
var filename = Settings.Instance.UseAccountNameAsMafileName ? login : steamId;
var step = new LinkAccountDoneStepVM(rCode, filename);
SetCurrentStep(step);
await step.GetResultAsync();
}
#endregion
private void SetCurrentStep(LinkAccountStepVM step)
{
Dispatcher.CurrentDispatcher.Invoke(() =>
{
CurrentStep = step;
Tip = CurrentStep.Tip;
});
}
[RelayCommand]
private void OpenTroubleshooting()
{
const string troubleshootingURI =
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
{
UseShellExecute = true
});
}
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
}
private static string GetLocalizationCommon(string key)
{
return LocManager.GetCommonOrDefault(key, key);
}
public void Dispose()
{
_client.Dispose();
_handler.Dispose();
try
{
_cts.Cancel();
}
catch
{
//Ignored, may be cancelled or disposed
}
_cts.Dispose();
}
#region Step 2: Email Code
public bool IsSupportedGuardType(ILoginConsumer consumer, EAuthSessionGuardType type)
{
return type == EAuthSessionGuardType.EmailCode;
}
// Step 2: Email Code
public async Task UpdateAuthSession(HttpClient authClient, ILoginConsumer loginConsumer,
UpdateAuthSessionModel model,
CancellationToken cancellationToken = default)
{
while (true)
{
try
{
var step = new LinkAccountEmailAuthStepVM();
SetCurrentStep(step);
var res = await step.GetResultAsync();
var req = AuthRequestHelper.CreateEmailCodeRequest(res, model.ClientId, model.SteamId);
await AuthenticationServiceApi.UpdateAuthSessionWithSteamGuardCode(authClient, req, cancellationToken);
Error = null;
break;
}
catch (SteamStatusCodeException ex)
when (ex.StatusCode.Equals(SteamStatusCode.InvalidLoginAuthCode))
{
Error = ExceptionHandler.GetExceptionString(ex);
}
}
}
#endregion
// @formatter:on
}
@@ -1,9 +1,7 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Utility;
namespace NebulaAuth.ViewModel.Linker;
@@ -13,10 +11,10 @@ public partial class LinkAccountDoneStepVM : LinkAccountStepVM
private readonly TaskCompletionSource _doneTcs = new();
private readonly string _rCode;
public LinkAccountDoneStepVM(string rCode, string steamId)
public LinkAccountDoneStepVM(string rCode, string fileName)
{
var tipStr = LocManager.GetCodeBehindOrDefault("MafileLinked", LinkAccountVM.LOCALIZATION_KEY, "MafileLinked");
InnerTip = string.Format(tipStr, rCode, steamId);
InnerTip = string.Format(tipStr, rCode, fileName);
_rCode = rCode;
}
@@ -44,13 +42,6 @@ public partial class LinkAccountDoneStepVM : LinkAccountStepVM
[RelayCommand]
private void CopyCode()
{
try
{
Clipboard.SetText(_rCode);
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Error whily copying RCode");
}
ClipboardHelper.Set(_rCode);
}
}
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
@@ -236,7 +235,7 @@ public partial class MafileMoverVM : ObservableObject, IAuthProvider, IDisposabl
var t = res.ReplacementToken;
var j = JsonConvert.SerializeObject(t);
Storage.BackupHandlerStr(login, j);
Storage.WriteBackup(login, j);
var mobileData = t.ToMobileDataExtended(SteamAuthenticatorLinkerApi.GenerateDeviceId(), msd);
@@ -253,34 +252,10 @@ public partial class MafileMoverVM : ObservableObject, IAuthProvider, IDisposabl
Logger.Error(ex, "Error during saving Nebula data to mafile");
}
Storage.SaveMafile(mafile);
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
await Storage.SaveMafileAsync(mafile);
await Done(mafile.RevocationCode ?? string.Empty, mafile.SteamId.Steam64.ToString());
}
#region Step 3: Sms code
// Step 3: Sms code
public Task<int> GetSms()
{
var step = new MafileMoverSmsStepVM();
SetCurrentStep(step);
return step.GetResultAsync();
}
#endregion
#region Step 4: Done
public async Task Done(string rCode, string steamId)
{
var step = new MafileMoverDoneStepVM(rCode, steamId);
SetCurrentStep(step);
await step.GetResultAsync();
}
#endregion
private void SetCurrentStep(MafileMoverStepVM step)
{
Dispatcher.CurrentDispatcher.Invoke(() =>
@@ -335,13 +310,15 @@ public partial class MafileMoverVM : ObservableObject, IAuthProvider, IDisposabl
_cts.Dispose();
}
#region Step 2: Guard Code
public bool IsSupportedGuardType(ILoginConsumer consumer, EAuthSessionGuardType type)
{
return type == EAuthSessionGuardType.DeviceCode;
}
// @formatter:off
#region Step 2: Guard Code
// Step 2: Guard Code
public async Task UpdateAuthSession(HttpClient authClient, ILoginConsumer loginConsumer,
UpdateAuthSessionModel model,
@@ -374,4 +351,29 @@ public partial class MafileMoverVM : ObservableObject, IAuthProvider, IDisposabl
}
#endregion
#region Step 3: Sms code
// Step 3: Sms code
public Task<int> GetSms()
{
var step = new MafileMoverSmsStepVM();
SetCurrentStep(step);
return step.GetResultAsync();
}
#endregion
#region Step 4: Done
public async Task Done(string rCode, string steamId)
{
var step = new MafileMoverDoneStepVM(rCode, steamId);
SetCurrentStep(step);
await step.GetResultAsync();
}
#endregion
// @formatter:on
}
+20 -24
View File
@@ -44,18 +44,12 @@ public partial class MainVM : ObservableObject
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
QueryGroups();
UpdateManager.CheckForUpdates();
if (Storage.DuplicateFound > 0)
{
SnackbarController.SendSnackbar(
GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
TimeSpan.FromSeconds(4));
}
}
[RelayCommand]
public async Task Debug()
{
Shell.Logger.Info("test");
await DialogsController.ShowSetAccountsPasswordDialog();
}
@@ -73,20 +67,21 @@ public partial class MainVM : ObservableObject
OnPropertyChanged(nameof(IsDefaultProxy));
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
OnPropertyChanged(nameof(IsMafileSelected));
RefreshSessionCommand.NotifyCanExecuteChanged();
LoginAgainCommand.NotifyCanExecuteChanged();
RemoveAuthenticatorCommand.NotifyCanExecuteChanged();
ConfirmLoginCommand.NotifyCanExecuteChanged();
}
}
[RelayCommand]
[RelayCommand(CanExecute = nameof(IsMafileSelected))]
public async Task LoginAgain()
{
if (SelectedMafile == null)
{
return;
}
var currentPassword = PHandler.DecryptPassword(SelectedMafile.Password);
var loginAgainVm = await DialogsController.ShowLoginAgainDialog(SelectedMafile.AccountName, currentPassword);
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
var currentPassword = PHandler.DecryptPassword(selectedMafile.Password);
var loginAgainVm = await DialogsController.ShowLoginAgainDialog(selectedMafile.AccountName, currentPassword);
if (loginAgainVm == null)
{
return;
@@ -97,7 +92,7 @@ public partial class MainVM : ObservableObject
var wait = DialogHost.Show(waitDialog);
try
{
await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword);
await MaClient.LoginAgain(selectedMafile, password, loginAgainVm.SavePassword);
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
}
catch (LoginException ex)
@@ -123,13 +118,14 @@ public partial class MainVM : ObservableObject
}
[RelayCommand]
[RelayCommand(CanExecute = nameof(IsMafileSelected))]
private async Task RefreshSession()
{
if (SelectedMafile == null) return;
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
try
{
await MaClient.RefreshSession(SelectedMafile);
await MaClient.RefreshSession(selectedMafile);
SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed"));
}
catch (Exception ex) when (ExceptionHandler.Handle(ex))
@@ -150,7 +146,7 @@ public partial class MainVM : ObservableObject
await DialogsController.ShowMafileMoverDialog();
}
[RelayCommand]
[RelayCommand(CanExecute = nameof(IsMafileSelected))]
private async Task RemoveAuthenticator()
{
var selectedMafile = SelectedMafile;
@@ -191,14 +187,14 @@ public partial class MainVM : ObservableObject
}
}
[RelayCommand]
[RelayCommand(CanExecute = nameof(IsMafileSelected))]
private async Task ConfirmLogin()
{
if (SelectedMafile == null) return;
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
try
{
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(selectedMafile), selectedMafile);
if (res.Success)
{
SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
@@ -14,7 +14,7 @@ namespace NebulaAuth.ViewModel;
public partial class MainVM //Confirmations
{
public ObservableCollection<Confirmation> Confirmations { get; } = new();
public ObservableCollection<Confirmation> Confirmations { get; } = [];
public bool ConfirmationsVisible => SelectedMafile == _confirmationsLoadedForMafile;
private Mafile? _confirmationsLoadedForMafile;
+4 -4
View File
@@ -30,11 +30,11 @@ public partial class MainVM //File //TODO: Refactor
{
var mafile = SelectedMafile;
var path = Storage.MafileFolder;
var path = Storage.MafilesDirectory;
string? mafilePath = null;
if (mafile != null)
{
mafilePath = Storage.TryFindMafilePath(mafile);
mafilePath = Storage.TryGetMafilePath(mafile);
}
if (mafilePath != null)
@@ -181,7 +181,7 @@ public partial class MainVM //File //TODO: Refactor
var result = data.SessionData != null;
if (!result) return result;
Storage.SaveMafile(data);
await Storage.SaveMafileAsync(data);
{
ResetQuery();
SearchText = data.AccountName ?? string.Empty;
@@ -266,7 +266,7 @@ public partial class MainVM //File //TODO: Refactor
private void CopyMafile(object? mafile)
{
if (mafile is not Mafile maf) return;
var path = Storage.TryFindMafilePath(maf);
var path = Storage.TryGetMafilePath(maf);
if (ClipboardHelper.SetFiles([path]))
SnackbarController.SendSnackbar(GetLocalization("MafileCopied"));
}
+17 -2
View File
@@ -1,8 +1,10 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using AchiesUtilities.Extensions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
@@ -33,9 +35,18 @@ public partial class MainVM //Groups
[ObservableProperty] private ObservableCollection<string> _groups = [];
private string _searchText = string.Empty;
private string? _selectedGroup;
[RelayCommand]
private async Task CreateGroup(Mafile? mafile)
{
if (mafile == null) return;
var res = await DialogsController.ShowTextFieldDialog(GetLocalization("CreateGroupTitle"),
GetLocalization("CreateGroupInput"));
if (string.IsNullOrWhiteSpace(res)) return;
AddToGroup([res, mafile]);
SnackbarController.SendSnackbar(GetLocalization("CreateGroupSuccess"));
}
[RelayCommand]
private void AddGroup(string? value)
@@ -68,7 +79,7 @@ public partial class MainVM //Groups
OnPropertyChanged(nameof(SelectedMafile));
}
[RelayCommand]
[RelayCommand(CanExecute = nameof(RemoveGroupCanExecute))]
private void RemoveGroup(Mafile? mafile)
{
if (mafile?.Group == null) return;
@@ -85,6 +96,10 @@ public partial class MainVM //Groups
PerformQuery();
}
private bool RemoveGroupCanExecute(Mafile? mafile)
{
return mafile is {Group: not null};
}
private void QueryGroups()
{
+3 -3
View File
@@ -86,12 +86,12 @@ public partial class MainVM //MAAC
{
var timerCheckSeconds = Settings.TimerSeconds;
if (timerCheckSeconds == value) return;
if (timerCheckSeconds < 10)
if (timerCheckSeconds < 5)
{
timerCheckSeconds = 10; //Guard
timerCheckSeconds = 5; //Guard
}
if (value < 10)
if (value < 5)
{
value = timerCheckSeconds;
SnackbarController.SendSnackbar(GetLocalization("TimerTooFast"));
@@ -18,11 +18,31 @@ public partial class ProxyManagerVM : ObservableObject
private static readonly Regex IdRegex = new(@"\{(\d+)\}$", RegexOptions.Compiled);
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
public bool AnyChanges { get; private set; }
public bool DisplayProtocol
{
get => Settings.Instance.ProxyManagerDisplayProtocol;
set
{
Settings.Instance.ProxyManagerDisplayProtocol = value;
OnPropertyChanged();
}
}
public bool DisplayCredentials
{
get => Settings.Instance.ProxyManagerDisplayCredentials;
set
{
Settings.Instance.ProxyManagerDisplayCredentials = value;
OnPropertyChanged();
}
}
[ObservableProperty] private string _addProxyField = string.Empty;
[ObservableProperty] private KeyValuePair<int, ProxyData>? _defaultProxy;
[ObservableProperty] private string? _errorText;
[ObservableProperty] private KeyValuePair<int, ProxyData>? _selectedProxy;
public ProxyManagerVM()
@@ -42,6 +62,7 @@ public partial class ProxyManagerVM : ObservableObject
var split = input
.Split(Environment.NewLine)
.Where(s => string.IsNullOrWhiteSpace(s) == false)
.Select(x => x.Trim())
.ToArray();
if (split.Length == 0) return;
@@ -66,7 +87,7 @@ public partial class ProxyManagerVM : ObservableObject
idPresent ??= idMatch.Success;
if (idPresent.Value != idMatch.Success)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
SetError(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
return;
}
@@ -75,7 +96,7 @@ public partial class ProxyManagerVM : ObservableObject
{
if (id != null && proxies.Any(kvp => kvp.Key == id))
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
SetError(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
return;
}
@@ -85,21 +106,32 @@ public partial class ProxyManagerVM : ObservableObject
{
if (split.Length == 1)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
SetError(GetLocalizationOrDefault("WrongFormat"));
return;
}
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
SetError(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
return;
}
}
ProxyStorage.SetProxies(proxies);
ProxyStorage.OrderCollection();
ProxyStorage.SortCollection();
AddProxyField = string.Empty;
SetError(null);
CheckIfDefaultProxyStay();
}
private void SetError(string? err)
{
ErrorText = err;
}
[RelayCommand]
public void ClearError()
{
SetError(null);
}
private void CheckIfDefaultProxyStay()
{
@@ -109,12 +141,13 @@ public partial class ProxyManagerVM : ObservableObject
}
[RelayCommand]
private void RemoveProxy()
private void RemoveProxy(object? target)
{
var targetProxy = target as KeyValuePair<int, ProxyData>? ?? SelectedProxy;
AnyChanges = true;
var selected = SelectedProxy;
if (selected == null) return;
var s = selected.Value;
if (targetProxy == null) return;
var s = targetProxy.Value;
KeyValuePair<int, ProxyData>? nextNeighbor = null;
@@ -0,0 +1,129 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using SteamLibForked.Models.SteamIds;
namespace NebulaAuth.ViewModel.Other;
public partial class SetAccountPasswordsVM : ObservableObject
{
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SetPasswordsCommand))]
private string? _accountsPasswords;
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SetEncryptionPasswordCommand))]
private string? _encryptionPassword;
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SetPasswordsCommand))]
private bool _isEncryptionPasswordSet;
[ObservableProperty] private string? _tip;
public SetAccountPasswordsVM()
{
_isEncryptionPasswordSet = PHandler.IsPasswordSet;
}
[RelayCommand(CanExecute = nameof(SetEncryptionPasswordCanExecute))]
private void SetEncryptionPassword()
{
if (string.IsNullOrWhiteSpace(EncryptionPassword))
{
return;
}
Settings.Instance.IsPasswordSet = PHandler.SetPassword(EncryptionPassword);
IsEncryptionPasswordSet = PHandler.IsPasswordSet;
EncryptionPassword = null;
}
private bool SetEncryptionPasswordCanExecute()
{
return !string.IsNullOrWhiteSpace(EncryptionPassword);
}
[RelayCommand(CanExecute = nameof(SetPasswordsCanExecute))]
private async Task SetPasswords()
{
Tip = null;
var input = AccountsPasswords;
if (string.IsNullOrWhiteSpace(input)) return;
var lines = input.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var success = 0;
var errors = 0;
var notFound = 0;
var mafs = Storage.MaFiles.ToList();
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
try
{
var split = line.Split(":", 2);
if (split.Length != 2)
{
errors++;
continue;
}
var login = split[0];
var password = split[1];
SteamId64? steamId = null;
if (SteamId64.TryParse(login, out var id64))
{
steamId = id64;
}
var maf = steamId != null
? mafs.FirstOrDefault(m => m.SteamId == steamId || m.AccountName == login)
: mafs.FirstOrDefault(m => string.Equals(m.AccountName, login, StringComparison.OrdinalIgnoreCase));
if (maf == null)
{
notFound++;
continue;
}
maf.Password = PHandler.Encrypt(password);
await Storage.UpdateMafileAsync(maf);
success++;
}
catch
{
errors++;
}
}
if (success > 0 && errors == 0 && notFound == 0)
{
Tip = string.Format(GetLocalization("Success"), success);
}
else
{
Tip = string.Format(GetLocalization("PartialSuccess"), success, notFound, errors);
}
}
private bool SetPasswordsCanExecute()
{
return !string.IsNullOrWhiteSpace(AccountsPasswords) && IsEncryptionPasswordSet;
}
[RelayCommand]
private void ClearTip()
{
Tip = null;
}
private string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, "SetAccountPasswordsVM", key);
}
}
+102 -23
View File
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -11,6 +13,93 @@ public partial class SettingsVM : ObservableObject
{
public Settings Settings => Settings.Instance;
[ObservableProperty] private string? _password;
[ObservableProperty] private double _renameMafilesProgress;
[ObservableProperty] private string? _renameResultText;
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ApplyRenameSettingCommand))]
private bool _useAccountNameAsMafileNamePreview;
public SettingsVM()
{
_useAccountNameAsMafileNamePreview = Settings.UseAccountNameAsMafileName;
}
[RelayCommand]
private void SetPassword()
{
Settings.IsPasswordSet = PHandler.SetPassword(Password);
}
[RelayCommand]
private void ResetThemeDefaults()
{
Settings.ResetThemeDefaults();
OnPropertyChanged(nameof(BackgroundBlur));
OnPropertyChanged(nameof(BackgroundOpacity));
OnPropertyChanged(nameof(BackgroundGamma));
OnPropertyChanged(nameof(LeftOpacity));
OnPropertyChanged(nameof(RightOpacity));
OnPropertyChanged(nameof(ApplyBlurBackground));
OnPropertyChanged(nameof(RippleDisabled));
}
[RelayCommand(CanExecute = nameof(ApplyRenameSettingCanExecute))]
private async Task ApplyRenameSetting()
{
RenameResultText = null;
var targetValue = UseAccountNameAsMafileNamePreview;
if (UseAccountNameAsMafileName == targetValue) return;
RenameMafilesProgress = 0;
Storage.MafileRenameResult? result = null;
try
{
result = await Storage.RenameMafiles(targetValue, new Progress<double>(p => RenameMafilesProgress = p));
UseAccountNameAsMafileName = targetValue;
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Error while renaming mafiles");
}
finally
{
RenameMafilesProgress = 0;
}
if (result == null)
{
RenameResultText = GetLoc("Error");
return;
}
if (result.Total == 0) return;
if (result.NotRenamed == 0)
{
var l = GetLoc("AllRenamed");
RenameResultText = string.Format(l, result.Total, result.BackupFileName);
}
else
{
var l = GetLoc("PartialSuccess");
RenameResultText =
string.Format(l, result.Total, result.Renamed, result.Errors, result.Conflict, result.BackupFileName);
}
string GetLoc(string key)
{
return LocManager.GetCodeBehindOrDefault(key, "SettingsVM", "MafileRenaming", key);
}
}
private bool ApplyRenameSettingCanExecute()
{
return UseAccountNameAsMafileNamePreview != UseAccountNameAsMafileName;
}
#region SettingsProps
public bool HideToTray
{
@@ -88,7 +177,11 @@ public partial class SettingsVM : ObservableObject
public bool UseAccountNameAsMafileName
{
get => Settings.UseAccountNameAsMafileName;
set => Settings.UseAccountNameAsMafileName = value;
set
{
Settings.UseAccountNameAsMafileName = value;
ApplyRenameSettingCommand.NotifyCanExecuteChanged();
}
}
public bool IgnorePatchTuesdayErrors
@@ -97,27 +190,7 @@ public partial class SettingsVM : ObservableObject
set => Settings.IgnorePatchTuesdayErrors = value;
}
[ObservableProperty] private string? _password;
[RelayCommand]
private void SetPassword()
{
Settings.IsPasswordSet = PHandler.SetPassword(Password);
}
[RelayCommand]
private void ResetThemeDefaults()
{
Settings.ResetThemeDefaults();
OnPropertyChanged(nameof(BackgroundBlur));
OnPropertyChanged(nameof(BackgroundOpacity));
OnPropertyChanged(nameof(BackgroundGamma));
OnPropertyChanged(nameof(LeftOpacity));
OnPropertyChanged(nameof(RightOpacity));
OnPropertyChanged(nameof(ApplyBlurBackground));
}
#endregion
#region Theme
@@ -169,5 +242,11 @@ public partial class SettingsVM : ObservableObject
set => Settings.ThemeType = value;
}
public bool RippleDisabled
{
get => Settings.RippleDisabled;
set => Settings.RippleDisabled = value;
}
#endregion
}
+250 -78
View File
@@ -40,6 +40,26 @@
"ru": "Прокси",
"ua": "Проксі"
},
"Delete": {
"en": "Delete",
"ru": "Удалить",
"ua": "Видалити"
},
"Cancel": {
"en": "Cancel",
"ru": "Отмена",
"ua": "Скасувати"
},
"OK": {
"en": "OK",
"ru": "ОК",
"ua": "ОК"
},
"Save": {
"en": "Save",
"ru": "Сохранить",
"ua": "Зберегти"
},
"Abbreviations": {
"Time": {
"Seconds": {
@@ -50,7 +70,7 @@
},
"Count": {
"Items": {
"en": "items",
"en": "pcs",
"ru": "шт",
"ua": "шт"
}
@@ -73,9 +93,9 @@
"Menu": {
"File": {
"Caption": {
"en": "File",
"ru": "Файл",
"ua": "Файл"
"en": "Menu",
"ru": "Меню",
"ua": "Меню"
},
"Import": {
"en": "Import",
@@ -96,6 +116,23 @@
"en": "Settings",
"ru": "Настройки",
"ua": "Налаштування"
},
"ProxyManager": {
"en": "Proxy manager",
"ru": "Менеджер прокси",
"ua": "Менеджер проксі"
},
"Other": {
"Title": {
"en": "Other",
"ru": "Прочее",
"ua": "Інше"
},
"SetPasswords": {
"en": "Set passwords",
"ru": "Назначить пароли",
"ua": "Призначити паролі"
}
}
},
"Account": {
@@ -166,8 +203,8 @@
},
"MafileProxyInUse": {
"en": "Mafile proxy is in use",
"ru": "Используется прокси из mafile",
"ua": "Використовується проксі з mafile"
"ru": "Используется прокси из мафайла",
"ua": "Використовується проксі з мафайла"
}
}
},
@@ -224,15 +261,16 @@
}
},
"Footer": {
"Account": {
"en": "Account: ",
"ru": "Аккаунт: ",
"ua": "Акаунт: "
"AccountsCount": {
"en": "Accounts: ",
"ru": "Аккаунты: ",
"ua": "Акаунти: "
},
"Group": {
"en": "Group: ",
"ru": "Группа: ",
"ua": "Група: "
"SelectedAccount": {
"en": "Current: ",
"ru": "Текущий: ",
"ua": "Поточний: "
}
},
"ConfirmationTemplates": {
@@ -252,9 +290,9 @@
"ua": "Api ключ"
},
"Market": {
"en": "Market",
"ru": "Маркет",
"ua": "Маркет"
"en": "Sell",
"ru": "Продажа",
"ua": "Продаж"
},
"Purchase": {
"en": "Purchase",
@@ -284,6 +322,11 @@
"ru": "Скопировать SteamID",
"ua": "Скопіювати SteamID"
},
"CreateGroup": {
"en": "Create group",
"ru": "Создать группу",
"ua": "Створити групу"
},
"AddToGroup": {
"en": "Add to group",
"ru": "Добавить в группу",
@@ -366,6 +409,11 @@
"ru": "Размытие при открытии диалог. окна",
"ua": "Розмиття при відкритті діалог. вікна"
},
"RippleDisabled": {
"en": "Disable ripple animations",
"ru": "Отключить анимации «ripple»",
"ua": "Вимкнути анімації «ripple»"
},
"Reset": {
"en": "Reset",
"ru": "Сбросить",
@@ -400,9 +448,9 @@
"ua": "Згортати в трей"
},
"UseIndicator": {
"en": "Use indicator",
"ru": "Использовать индикатор",
"ua": "Використовувати індикатор"
"en": "Use color indicator in taskbar",
"ru": "Цветной индикатор в панели задач",
"ua": "Кольоровий індикатор в панелі завдань"
},
"UseIndicatorHint": {
"en": "Small color indicator in windows toolbar to identify program instance",
@@ -421,7 +469,7 @@
"ua": "Поточний пароль шифрування"
},
"Hint": {
"en": "Doesn't saved on disk",
"en": "Not saved to disk",
"ru": "Не сохраняется в системе",
"ua": "Не зберігається у системі"
}
@@ -442,10 +490,15 @@
"ru": "Разрешить автообновление",
"ua": "Дозволити автооновлення"
},
"UseAccountName": {
"en": "Use account name on mafiles",
"ru": "Использовать имя аккаунта на мафайлах",
"ua": "Використовувати ім'я акаунта на мафайлах"
"MafileNamingMode": {
"en": "Mafile naming mode",
"ru": "Режим именования мафайлов",
"ua": "Режим іменування мафайлів"
},
"ApplyNamingMode": {
"en": "Apply and rename",
"ru": "Применить и переименовать",
"ua": "Застосувати та перейменувати"
},
"IgnorePatchTuesdayErrors": {
"en": "Ignore Patch Tuesday errors in timer (exp.)",
@@ -520,6 +573,16 @@
"en": "Use random default proxy",
"ru": "Cлучайный прокси по умолчанию",
"ua": "Випадковий проксі за замовчуванням"
},
"DisplayProxyProtocol": {
"en": "Display protocol",
"ru": "Отображать протокол",
"ua": "Відображати протокол"
},
"DisplayProxyCredentials": {
"en": "Display credentials",
"ru": "Отображать логин и пароль",
"ua": "Відображати логін та пароль"
}
},
"WaitLoginDialog": {
@@ -626,7 +689,7 @@
"ua": "СМС код"
}
},
"SetEncryptedPasswordDialog": {
"SetEncryptionPasswordDialog": {
"Title": {
"en": "Encryption password",
"ru": "Пароль шифрования",
@@ -634,11 +697,11 @@
},
"DialogText": {
"en":
"You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session. (Optional)",
"You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session.\r\n(Optional)",
"ru":
"Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией. (Не обязательно)",
"Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)",
"ua":
"Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією. (Не обов'язково)"
"Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)"
},
"Ok": {
"en": "Ok",
@@ -680,6 +743,53 @@
"ua": "Steam Guard перенесено"
}
},
"ConfirmCancelDialog": {
"Title": {
"ru": "Подтвердите действие",
"en": "Confirm action",
"ua": "Підтвердіть дію"
}
},
"TextFieldDialog": {
"Title": {
"ru": "Введите значение",
"en": "Enter value",
"ua": "Введіть значення"
}
},
"SetAccountPasswordsView": {
"Title": {
"en": "Set passwords",
"ru": "Назначить пароли",
"ua": "Призначити паролі"
},
"EncryptionPassword": {
"en": "Encryption password:",
"ru": "Пароль шифрования:",
"ua": "Пароль шифрування:"
},
"EnterPassword": {
"en": "Enter password",
"ru": "Введите пароль",
"ua": "Введіть пароль"
},
"Status": {
"en": "Status:",
"ru": "Статус:",
"ua": "Статус:"
},
"EncryptionHint": {
"en": "All passwords in mafiles will be encrypted using the encryption password.",
"ru": "Все пароли в мафайлах будут зашифрованы с помощью пароля шифрования.",
"ua": "Всі паролі в мафайлах будуть зашифровані за допомогою пароля шифрування."
},
"InputFormat": {
"en": "Supported formats:\r\n\r\nlogin:password\r\nsteamid:password",
"ru": "Поддерживаемые форматы:\r\n\r\nlogin:password\r\nsteamid:password",
"ua": "Підтримувані формати:\r\n\r\nlogin:password\r\nsteamid:password"
}
},
"CodeBehind": {
"MainVM": {
"SuccessfulLogin": {
@@ -698,9 +808,10 @@
"ua": "Відсутній RCode"
},
"ConfirmRemovingAuthenticator": {
"ru": "Вы точно уверены что хотите удалить аутентификатор?",
"en": "Are you sure you want to remove authenticator?",
"ua": "Ви точно впевнені що хочете видалити аутентифікатор?"
"ru": "Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда",
"en": "Are you sure you want to remove the authenticator?\r\nAfter that, you will not be able to trade or use the marketplace for 15 days. Mafile will be deleted permanently",
"ua": "Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди"
},
"AuthenticatorRemoved": {
"ru": "Аутентификатор удален",
@@ -733,12 +844,12 @@
"ua": "Помилка підтвердження"
},
"ConfirmMafileOverwrite": {
"ru": "Внимание, мафайл уже присутствует в папке mafiles.\r\nПерезаписать мафайлы, которые конфликтуют?",
"en": "Attention, mafile already exists in mafiles folder.\r\nOverwrite conflicting mafiles?",
"ua": "Увага, мафайл вже присутній у папці mafiles.\r\nПерезаписати мафайли, які конфліктують?"
"ru": "Внимание, мафайл(-ы) уже присутствует в папке mafiles.\r\nПерезаписать мафайлы, которые конфликтуют?",
"en": "Attention, mafile(-s) already exists in mafiles folder.\r\nOverwrite conflicting mafiles?",
"ua": "Увага, мафайл(-и) вже присутній у папці mafiles.\r\nПерезаписати мафайли, які конфліктують?"
},
"MafileImportError": {
"ru": "Ошибка импорта мафайла",
"ru": "Ошибка при импорте мафайла",
"en": "Mafile import error",
"ua": "Помилка імпорту мафайла"
},
@@ -768,17 +879,14 @@
"ua": "помилки:"
},
"RemoveMafileConfirmation": {
"ru":
"Удалить мафайл? Мафайл будет перемещен в папку 'mafiles_removed' (Это действие не удаляет Steam Guard с аккаунта)",
"en":
"Remove mafile? Mafile will be moved to 'mafiles_removed' directory (This action does not remove the Steam Guard from the account)",
"ua":
"Видалити мафайл? Мафайл буде переміщено у каталог 'mafiles_removed' (Ця дія не видаляє автентифікатор з облікового запису)"
"ru": "Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта",
"en": "Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account",
"ua": "Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису"
},
"CantRemoveAlreadyExist": {
"ru": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
"ru": "Не удалось переместить mafile, возможно в папке уже существует мафайл с таким именем.",
"en": "Can't move mafile, maybe mafile with same name already exists in mafiles_removed folder.",
"ua": "Не вдалося перемістити мафайл, можливо в папці вже існує мафайл з таким іменем."
"ua": "Не вдалося перемістити mafile, можливо в папці вже існує мафайл з таким іменем."
},
"CantRemoveMafile": {
"ru": "Не удалось удалить (переместить) мафайл:",
@@ -790,11 +898,6 @@
"en": "Too fast timer.",
"ua": "Занадто швидкий таймер."
},
"DuplicateMafilesFound": {
"en": "Duplicate mafile(s) found and were not loaded. Check log to get lists of duplicate files.",
"ru": "Найдены дубликаты мафайлов, они не были загружены. Проверьте лог чтобы получить списки дубликатов.",
"ua": "Знайдено дублікати мафайлів, вони не були завантажені. Перевірте лог щоб отримати списки дублікатів."
},
"TimerChanged": {
"en": "Timer changed",
"ru": "Таймер изменен",
@@ -832,9 +935,23 @@
},
"CantDecryptPassword": {
"en": "Can't decrypt password. Maybe password from this mafile was encrypted with another encryption password",
"ru":
"Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
"ru": "Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
"ua": "Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування"
},
"CreateGroupTitle": {
"en": "New group",
"ru": "Новая группа",
"ua": "Нова група"
},
"CreateGroupInput": {
"en": "Enter group name",
"ru": "Введите имя группы",
"ua": "Введіть назву групи"
},
"CreateGroupSuccess": {
"en": "Group created",
"ru": "Группа создана",
"ua": "Групу створено"
}
},
"ErrorTranslator": {
@@ -1089,10 +1206,8 @@
},
"InvalidStateWithStatus2": {
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
"en":
"Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
"ua":
"Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна."
"en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна."
},
"GeneralFailure": {
"ru": "General Failure",
@@ -1132,6 +1247,23 @@
"en": "Login error: ",
"ua": "Помилка входу: "
},
"UnsupportedAuthTypeException": {
"Mail": {
"ru": "Ошибка: Был запрошен код с почты. Steam Guard не активирован",
"en": "Error: Email code was requested. Steam Guard is not activated",
"ua": "Помилка: Був запрошений код з пошти. Steam Guard не активовано"
},
"Guard": {
"ru": "Ошибка: Был запрошен вход с помощью Steam Guard. На аккаунте уже активирован мобильный аутентификатор",
"en": "Error: Login with Steam Guard was requested. Mobile authenticator is already activated on the account",
"ua": "Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор"
},
"Unknown": {
"ru": "Ошибка: Неизвестный тип аутентификации",
"en": "Error: Unknown authentication type",
"ua": "Помилка: Невідомий тип автентифікації"
}
},
"UnknownException": {
"ru": "Неизвестная ошибка: ",
"en": "Unknown error: ",
@@ -1227,12 +1359,9 @@
"ua": "На пошту надіслано лист. Відкрийте посилання з листа, а потім натисніть — 'Продовжити'"
},
"ClickOnEmailLinkRetry": {
"ru":
"Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
"en":
"We couldnt verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
"ua":
"Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'"
"ru": "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
"en": "We couldnt verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
"ua": "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'"
},
"PhoneHint": {
"ru": "На телефон {0} была отправлена СМС",
@@ -1240,12 +1369,9 @@
"ua": "На телефон {0} було відправлено СМС"
},
"EnterPhoneNumber": {
"ru":
"Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
"en":
"Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
"ua":
"Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера"
"ru": "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
"en": "Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
"ua": "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера"
}
},
"MafileMoverVM": {
@@ -1270,11 +1396,9 @@
"ua": "RCode: {0}\r\nІм'я файлу: {1}.mafile"
},
"GuardIsNotActive": {
"ru":
"Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
"ru": "Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
"en": "Steam Guard is set on this account. To link mafile to the account, use the 'Link' window",
"ua":
"Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'"
"ua": "Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'"
},
"SeemsNoPhoneNumber": {
"en": "The account has no phone number linked, returned Fail code (2)",
@@ -1309,14 +1433,62 @@
"ua": "Авто "
},
"TimerPreventedOverlap": {
"ru":
"Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
"en":
"Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"ua":
"Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку"
"ru": "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
"en": "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"ua": "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку"
},
"FailedToLoadStorage": {
"en": "Failed to load auto-confirm timers. File seems to be corrupted",
"ru": "Не удалось загрузить таймеры авто-подтверждений. Файл, похоже, поврежден",
"ua": "Не вдалося завантажити таймери авто-підтверджень. Файл, схоже, пошкоджено"
}
}
},
"SettingsVM": {
"MafileRenaming": {
"Error": {
"en": "Error while renaming mafiles. It was saved in log",
"ru": "Ошибка при переименовании мафайлов. Она была сохранена в log",
"ua": "Помилка при перейменуванні мафайлів. Вона була збережена в log"
},
"AllRenamed": {
"ru": "Все мафайлы успешно переименованы ({0}).\r\nБыла создана резервная копия: {1}",
"en": "All mafiles successfully renamed ({0}).\r\nBackup was created: {1}",
"ua": "Всі мафайли успішно перейменовані ({0}).\r\nБуло створено резервну копію: {1}"
},
"PartialSuccess": {
"en": "Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}",
"ru": "Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}",
"ua": "Всього мафайлів: {0}\r\nПерейменовано: {1}\r\nКонфлікти: {2}\r\nПомилок: {3}\r\n\r\nРезервна копія мафайлів збережена у файлі: {4}"
}
}
},
"ProxyStorage": {
"ErrorWhileLoadingProxies": {
"en": "Error while loading proxies",
"ru": "Ошибка при загрузке прокси",
"ua": "Помилка при завантаженні проксі"
}
},
"Settings": {
"ErrorWhileLoadingSettings": {
"en": "Error while loading settings. Settings were reset",
"ru": "Ошибка при загрузке настроек. Настройки были сброшены",
"ua": "Помилка при завантаженні налаштувань. Налаштування були скинуті"
}
},
"SetAccountPasswordsVM": {
"Success": {
"en": "Passwords successfully set for {0} mafiles",
"ru": "Пароли успешно установлены для {0} мафайлов",
"ua": "Паролі успішно встановлені для {0} мафайлів"
},
"PartialSuccess": {
"en": "Success: {0}, Not found: {1}, Errors: {2}",
"ru": "Успешно: {0}, Не найдено: {1}, Ошибки: {2}",
"ua": "Успішно: {0}, Не знайдено: {1}, Помилки: {2}"
}
}
},
"CantAlignTimeError": {
"ru":
+4 -4
View File
@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.3.3" />
<PackageReference Include="AchiesUtilities.Web" Version="1.3.2" />
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.3.8" />
<PackageReference Include="AchiesUtilities.Web" Version="1.3.8" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="protobuf-net" Version="3.2.52" />
@@ -2,6 +2,8 @@
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions_005Cauthorization/@EntryIndexedValue">False</s:Boolean>
<s:Boolean
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Ccore/@EntryIndexedValue">True</s:Boolean>
<s:Boolean
+8 -8
View File
@@ -6,13 +6,13 @@ namespace SteamLib.Utility;
public static partial class SteamIdParser
{
[GeneratedRegex("^7656119([0-9]{10})$", RegexOptions.Compiled)]
public static partial Regex Steam64Regex { get; }
public static partial Regex Steam64Regex();
[GeneratedRegex(@"STEAM_(?<universe>[0-9]):(?<lowestBit>[0-9]):(?<highestBits>[0-9]{1,10})", RegexOptions.Compiled)]
public static partial Regex Steam2Regex { get; }
public static partial Regex Steam2Regex();
[GeneratedRegex(@"^\[?(?<type>[a-zA-Z]):1:(?<id>[0-9]{1,10})\]$", RegexOptions.Compiled)]
public static partial Regex Steam3Regex { get; }
public static partial Regex Steam3Regex();
#region TryParse
@@ -46,7 +46,7 @@ public static partial class SteamIdParser
{
result = default;
if (input == null) return false;
var match64 = Steam64Regex.Match(input);
var match64 = Steam64Regex().Match(input);
if (match64.Success)
{
return TryParse64(long.Parse(match64.Value), out result);
@@ -69,7 +69,7 @@ public static partial class SteamIdParser
public static bool TryParse2(string input, out SteamId2 result)
{
var match2 = Steam2Regex.Match(input);
var match2 = Steam2Regex().Match(input);
if (match2.Success)
{
var universe = byte.Parse(match2.Groups["universe"].Value);
@@ -85,7 +85,7 @@ public static partial class SteamIdParser
public static bool TryParse3(string input, out SteamId3 result)
{
var match3 = Steam3Regex.Match(input);
var match3 = Steam3Regex().Match(input);
if (match3.Success)
{
var type = match3.Groups["type"].Value[0];
@@ -137,7 +137,7 @@ public static partial class SteamIdParser
public static SteamId2 Parse2(string input)
{
var match2 = Steam2Regex.Match(input);
var match2 = Steam2Regex().Match(input);
if (match2.Success)
{
var universe = byte.Parse(match2.Groups["universe"].Value);
@@ -151,7 +151,7 @@ public static partial class SteamIdParser
public static SteamId3 Parse3(string input)
{
var match3 = Steam3Regex.Match(input);
var match3 = Steam3Regex().Match(input);
if (match3.Success)
{
var type = match3.Groups["type"].Value[0];