mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-26 14:51:42 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad1df3227b | |||
| 05f132ab1b | |||
| ee858921dc | |||
| 750292bfba | |||
| 982bb4d0c8 | |||
| 8e5fea54cd | |||
| 882d39b8f3 | |||
| ffcc7405a7 | |||
| b3e7436e95 | |||
| d319fc19f9 | |||
| a56757302e |
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
@@ -360,4 +360,6 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
|
||||
todo/
|
||||
@@ -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}"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
||||
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+2
-2
@@ -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
|
||||
{
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,11 +0,0 @@
|
||||
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
|
||||
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
|
||||
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
|
||||
• Добавить полное шифрование мафайлов по аналогии с SDA
|
||||
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
||||
• Антик-окно как в MarketApp, возможность сразу открыть браузер напр. на CefSharp с залогиненым аккаунтом
|
||||
• Стабильность авто-подтверждений, возможность задать пользователю порог времени когда льются ошибки и только тогда выключать авто-подтверждение
|
||||
• Безопасное сохранение мафайлов через .tmp / .bak
|
||||
• Создание механизма накопления ошибок, чтобы исправить Patch Tuesday, условно аккаунт может игнорировать ошибки 1-2 часа (настриавемо)
|
||||
|
||||
• Исправить "Перенос гуарда". Иногда не хочет принимать код / подтверждение
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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}"
|
||||
|
||||
@@ -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>
|
||||
@@ -1,8 +1,5 @@
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LinkerView.xaml
|
||||
/// </summary>
|
||||
public partial class LinkerView
|
||||
{
|
||||
public LinkerView()
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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='
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='
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
IP:PORT:USER:PASS
PROTOCOL://IP:PORT:USER:PASS
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>
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -5,9 +5,6 @@ using MaterialDesignThemes.Wpf;
|
||||
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView
|
||||
{
|
||||
private readonly DialogHost? _dialogHost;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 couldn’t verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
|
||||
"ua":
|
||||
"Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'"
|
||||
"ru": "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
|
||||
"en": "We couldn’t 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":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user