feat(confirmations): reveal items in grouped market sell confirmations (#18)

Grouped market sell confirmations previously appeared as a single row showing only the item count (e.g. "Sell 4 pcs"). This made it difficult to verify which items were being confirmed or their prices.

Add an expandable grouped confirmation that reveals individual items with their icon, name and price. Also allow confirming or canceling items individually while keeping confirm-all and cancel-all actions.

Includes a scrollable list for large batches and minor UI/code cleanup.

Co-authored-by: bohdanbtw [bohdanbtw@DESKTOP-ANGF2KJ](mailto:bohdanbtw@DESKTOP-ANGF2KJ)
This commit is contained in:
bohdanbtw
2026-03-12 15:27:03 +00:00
committed by GitHub
parent 053faec343
commit 4b547e19c4
6 changed files with 182 additions and 50 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
@@ -130,7 +130,7 @@ public static class DialogsController
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
return result != null && (bool)result;
}
public static async Task<string?> ShowTextFieldDialog(string? title = null, string? msg = null)
+3 -3
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -51,7 +51,7 @@ public static class Storage
await Task.Run(() =>
{
return Parallel.ForEachAsync(files,
new ParallelOptions {CancellationToken = token, MaxDegreeOfParallelism = threadCount},
new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = threadCount },
async (file, ct) =>
{
try
@@ -218,7 +218,7 @@ public static class Storage
if (counter % 5 == 0)
{
progress?.Report(counter / (double) files.Count);
progress?.Report(counter / (double)files.Count);
await Task.Delay(10);
}
}
+91 -32
View File
@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
@@ -6,7 +6,6 @@
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
<Grid>
<Grid.ColumnDefinitions>
@@ -175,36 +174,96 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type entities:MarketMultiConfirmation}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<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}" />
<Run Text="{Binding Confirmations.Count, Mode=OneWay}" />
<Run Text="{Tr Common.Abbreviations.Count.Items, IsDynamic=False}" />
</TextBlock>
<Expander Padding="0" Background="Transparent" materialDesign:ExpanderAssist.ExpanderButtonPosition="Start" materialDesign:ExpanderAssist.HorizontalHeaderPadding="0" HorizontalAlignment="Stretch">
<Expander.Header>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<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}" />
<Run Text="{Binding Confirmations.Count, Mode=OneWay}" />
<Run Text="{Tr Common.Abbreviations.Count.Items, IsDynamic=False}" />
</TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0"
Text="{Binding Time, StringFormat=t}" />
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3"
Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Center" Margin="5,0,0,0"
Text="{Binding Time, StringFormat=t}" />
<!-- Confirm all -->
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="4"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="{Tr MainWindow.ConfirmationTemplates.ConfirmAll}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="CheckAll" />
</Button>
<!-- Cancel all -->
<Button Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
ToolTip="{Tr MainWindow.ConfirmationTemplates.CancelAll}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</Expander.Header>
<Border Margin="30,6,0,0" Background="{DynamicResource MaterialDesignPaper}" CornerRadius="4">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
MaxHeight="320">
<ItemsControl ItemsSource="{Binding Confirmations}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type confirmations:MarketConfirmation}">
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32"
MaxHeight="32" Margin="0,0,10,0"
Source="{Binding ItemImageUri}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding ItemName}" />
</StackPanel>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left"
Margin="8,0,0,0"
Text="{Binding Time, StringFormat=t}" />
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="{Tr MainWindow.ConfirmationTemplates.ConfirmOne}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding}">
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
ToolTip="{Tr MainWindow.ConfirmationTemplates.CancelOne}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding}">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Expander>
</DataTemplate>
<DataTemplate DataType="{x:Type confirmations:PurchaseConfirmation}">
<Grid>
@@ -227,7 +286,7 @@
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -76,18 +76,27 @@ public partial class MainVM //Confirmations
return SendConfirmation(SelectedMafile, confirmation, false);
}
private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
{
bool result;
try
{
if (confirmation is MarketMultiConfirmation multi)
switch (confirmation)
{
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
}
else
{
result = await MaClient.SendConfirmation(mafile, confirmation, confirm);
case MarketMultiConfirmation multi:
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
break;
case MarketConfirmation market:
result = await MaClient.SendConfirmation(mafile, market, confirm);
break;
default:
result = await MaClient.SendConfirmation(mafile, confirmation, confirm);
break;
}
}
catch (Exception ex)
@@ -96,13 +105,49 @@ public partial class MainVM //Confirmations
return;
}
if (result)
{
Confirmations.Remove(confirmation);
}
else
if (!result)
{
SnackbarController.SendSnackbar(GetLocalization("ConfirmationError"));
return;
}
UpdateConfirmationState(confirmation);
}
private void UpdateConfirmationState(Confirmation confirmation)
{
if (confirmation is MarketConfirmation item)
{
var parent = Confirmations
.OfType<MarketMultiConfirmation>()
.FirstOrDefault(p => p.Confirmations.Contains(item));
if (parent == null)
{
Confirmations.Remove(item);
return;
}
parent.Confirmations.Remove(item);
if (parent.Confirmations.Count == 0)
{
Confirmations.Remove(parent);
return;
}
if (parent.Confirmations.Count == 1)
{
var remaining = parent.Confirmations[0];
var idx = Confirmations.IndexOf(parent);
Confirmations[idx] = remaining;
return;
}
parent.Time = parent.Confirmations.FirstOrDefault()?.Time ?? parent.Time;
return;
}
Confirmations.Remove(confirmation);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
+28
View File
@@ -410,6 +410,34 @@
"zh": "购买",
"ua": "Покупка",
"fr": "Acheter"
},
"ConfirmAll": {
"en": "Confirm all",
"ru": "Подтвердить все",
"zh": "全部确认",
"ua": "Підтвердити все",
"fr": "Tout confirmer"
},
"CancelAll": {
"en": "Cancel all",
"ru": "Отклонить все",
"zh": "全部取消",
"ua": "Відхилити все",
"fr": "Tout annuler"
},
"ConfirmOne": {
"en": "Confirm this item",
"ru": "Подтвердить этот предмет",
"zh": "确认此物品",
"ua": "Підтвердити цей предмет",
"fr": "Confirmer cet objet"
},
"CancelOne": {
"en": "Cancel this item",
"ru": "Отклонить этот предмет",
"zh": "取消此物品",
"ua": "Відхилити цей предмет",
"fr": "Annuler cet objet"
}
},
"CodeCopied": {