Update progress messaging for consistency
This commit is contained in:
parent
35bde51e7d
commit
a74ebc810a
@ -177,7 +177,8 @@ public class DownloadOrchestrationService(
|
||||
if (config.DownloadAvatarHeaderPhoto)
|
||||
{
|
||||
eventHandler.CancellationToken.ThrowIfCancellationRequested();
|
||||
UserEntities.User? userInfo = await apiService.GetUserInfo($"/users/{username}", eventHandler.CancellationToken);
|
||||
UserEntities.User? userInfo =
|
||||
await apiService.GetUserInfo($"/users/{username}", eventHandler.CancellationToken);
|
||||
if (userInfo != null)
|
||||
{
|
||||
await downloadService.DownloadAvatarHeader(userInfo.Avatar, userInfo.Header, path, username);
|
||||
@ -256,7 +257,7 @@ public class DownloadOrchestrationService(
|
||||
: tempStories.Count;
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {tempStories.Count} Stories", totalSize, config.ShowScrapeSize,
|
||||
$"Downloading {tempStories.Count} stories", totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadStories(username, userId, path,
|
||||
PaidPostIds.ToHashSet(), reporter));
|
||||
|
||||
@ -285,7 +286,7 @@ public class DownloadOrchestrationService(
|
||||
: tempHighlights.Count;
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {tempHighlights.Count} Highlights", totalSize, config.ShowScrapeSize,
|
||||
$"Downloading {tempHighlights.Count} highlights", totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadHighlights(username, userId, path,
|
||||
PaidPostIds.ToHashSet(), reporter));
|
||||
|
||||
@ -359,9 +360,12 @@ public class DownloadOrchestrationService(
|
||||
long totalSize = config.ShowScrapeSize
|
||||
? await downloadService.CalculateTotalFileSize(post.SinglePosts.Values.ToList())
|
||||
: post.SinglePosts.Count;
|
||||
int postCount = post.SinglePostObjects.Count;
|
||||
string postLabel = postCount == 1 ? "Post" : "Posts";
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
"Downloading Post", totalSize, config.ShowScrapeSize,
|
||||
$"Downloading {post.SinglePosts.Count} media from {postCount} {postLabel.ToLowerInvariant()}", totalSize,
|
||||
config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadSinglePost(username, path, users,
|
||||
clientIdBlobMissing, devicePrivateKeyMissing, post, reporter));
|
||||
|
||||
@ -455,7 +459,7 @@ public class DownloadOrchestrationService(
|
||||
: purchasedTabCollection.PaidPosts.PaidPosts.Count;
|
||||
|
||||
DownloadResult postResult = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {purchasedTabCollection.PaidPosts.PaidPosts.Count} Media from {purchasedTabCollection.PaidPosts.PaidPostObjects.Count} Paid Posts",
|
||||
$"Downloading {purchasedTabCollection.PaidPosts.PaidPosts.Count} media from {purchasedTabCollection.PaidPosts.PaidPostObjects.Count} paid posts",
|
||||
totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadPaidPostsPurchasedTab(
|
||||
purchasedTabCollection.Username, path, users,
|
||||
@ -483,7 +487,7 @@ public class DownloadOrchestrationService(
|
||||
: purchasedTabCollection.PaidMessages.PaidMessages.Count;
|
||||
|
||||
DownloadResult msgResult = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {purchasedTabCollection.PaidMessages.PaidMessages.Count} Media from {purchasedTabCollection.PaidMessages.PaidMessageObjects.Count} Paid Messages",
|
||||
$"Downloading {purchasedTabCollection.PaidMessages.PaidMessages.Count} media from {purchasedTabCollection.PaidMessages.PaidMessageObjects.Count} paid messages",
|
||||
totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadPaidMessagesPurchasedTab(
|
||||
purchasedTabCollection.Username, path, users,
|
||||
@ -554,7 +558,7 @@ public class DownloadOrchestrationService(
|
||||
: totalCount;
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {totalCount} Media from {messageCount} {messageLabel} ({paidCount} Paid + {previewCount} Preview)",
|
||||
$"Downloading {totalCount} media from {messageCount} {messageLabel} ({paidCount} paid + {previewCount} preview)",
|
||||
totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadSinglePaidMessage(username, path, users,
|
||||
clientIdBlobMissing, devicePrivateKeyMissing, singlePaidMessageCollection, reporter));
|
||||
@ -574,7 +578,7 @@ public class DownloadOrchestrationService(
|
||||
: previewCount;
|
||||
|
||||
DownloadResult previewResult = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {previewCount} Preview Media from {messageCount} {messageLabel}",
|
||||
$"Downloading {previewCount} preview media from {messageCount} {messageLabel.ToLowerInvariant()}",
|
||||
previewSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadSinglePaidMessage(username, path, users,
|
||||
clientIdBlobMissing, devicePrivateKeyMissing, singlePaidMessageCollection, reporter));
|
||||
@ -594,7 +598,7 @@ public class DownloadOrchestrationService(
|
||||
: paidCount;
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {paidCount} Paid Media from {messageCount} {messageLabel}",
|
||||
$"Downloading {paidCount} paid media from {messageCount} {messageLabel.ToLowerInvariant()}",
|
||||
totalSize, config.ShowScrapeSize,
|
||||
async reporter => await downloadService.DownloadSinglePaidMessage(username, path, users,
|
||||
clientIdBlobMissing, devicePrivateKeyMissing, singlePaidMessageCollection, reporter));
|
||||
@ -658,7 +662,8 @@ public class DownloadOrchestrationService(
|
||||
: mediaCount;
|
||||
|
||||
DownloadResult result = await eventHandler.WithProgressAsync(
|
||||
$"Downloading {mediaCount} Media from {objectCount} {contentType}", totalSize, config.ShowScrapeSize,
|
||||
$"Downloading {mediaCount} media from {objectCount} {contentType.ToLowerInvariant()}", totalSize,
|
||||
config.ShowScrapeSize,
|
||||
async reporter => await downloadData(data, reporter));
|
||||
|
||||
eventHandler.OnDownloadComplete(contentType, result);
|
||||
|
||||
@ -12,6 +12,8 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
Func<bool> isCancellationRequested,
|
||||
CancellationToken cancellationToken) : IDownloadEventHandler
|
||||
{
|
||||
private string _lastProgressDescription = string.Empty;
|
||||
|
||||
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||
|
||||
public async Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work)
|
||||
@ -33,6 +35,7 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
Func<IProgressReporter, Task<T>> work)
|
||||
{
|
||||
ThrowIfCancellationRequested();
|
||||
_lastProgressDescription = description;
|
||||
progressStart(description, maxValue, showSize);
|
||||
try
|
||||
{
|
||||
@ -87,7 +90,8 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
public void OnScrapeComplete(TimeSpan elapsed)
|
||||
{
|
||||
ThrowIfCancellationRequested();
|
||||
activitySink($"Scrape completed in {elapsed.TotalMinutes:0.00} minutes.");
|
||||
string summary = BuildCompletionSummary(elapsed);
|
||||
activitySink(summary);
|
||||
}
|
||||
|
||||
public void OnMessage(string message)
|
||||
@ -103,4 +107,21 @@ internal sealed class AvaloniaDownloadEventHandler(
|
||||
throw new OperationCanceledException("Operation canceled by user.");
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildCompletionSummary(TimeSpan elapsed)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_lastProgressDescription))
|
||||
{
|
||||
return $"Download completed in {elapsed.TotalMinutes:0.0} minutes.";
|
||||
}
|
||||
|
||||
string normalized = _lastProgressDescription.Trim().TrimEnd('.');
|
||||
if (normalized.StartsWith("Downloading ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string remainder = normalized["Downloading ".Length..].ToLowerInvariant();
|
||||
return $"Downloaded {remainder} in {elapsed.TotalMinutes:0.0} minutes.";
|
||||
}
|
||||
|
||||
return $"{normalized} in {elapsed.TotalMinutes:0.0} minutes.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,8 +181,6 @@ public partial class ConfigFieldViewModel : ViewModelBase
|
||||
|
||||
[ObservableProperty] private string _textValue = string.Empty;
|
||||
|
||||
private bool _isNormalizingFileNameFormatInput;
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(InsertSelectedFileNameVariableCommand))]
|
||||
private string? _selectedFileNameVariable;
|
||||
|
||||
@ -370,18 +368,6 @@ public partial class ConfigFieldViewModel : ViewModelBase
|
||||
|
||||
partial void OnTextValueChanged(string value)
|
||||
{
|
||||
if (IsFileNameFormatField && !_isNormalizingFileNameFormatInput)
|
||||
{
|
||||
string trimmedValue = value.Trim();
|
||||
if (!string.Equals(value, trimmedValue, StringComparison.Ordinal))
|
||||
{
|
||||
_isNormalizingFileNameFormatInput = true;
|
||||
TextValue = trimmedValue;
|
||||
_isNormalizingFileNameFormatInput = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Store actual value if not the privacy placeholder
|
||||
if (value != "[Hidden for Privacy]")
|
||||
{
|
||||
|
||||
@ -19,7 +19,6 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
|
||||
private readonly Action<bool> _onClose;
|
||||
private readonly Func<bool> _isUsernameDuplicate;
|
||||
private bool _isNormalizingUsername;
|
||||
private bool _isNormalizingFileNameFormat;
|
||||
|
||||
[ObservableProperty] private bool _isOpen;
|
||||
[ObservableProperty] private bool _isEditMode;
|
||||
@ -238,30 +237,22 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
partial void OnPaidPostFileNameFormatChanged(string value) =>
|
||||
NormalizeFileNameFormat(
|
||||
value,
|
||||
trimmed => PaidPostFileNameFormat = trimmed,
|
||||
HandleFileNameFormatChanged(
|
||||
() => PaidPostFileNameFormatError = string.Empty,
|
||||
UpdatePaidPostPreview);
|
||||
|
||||
partial void OnPostFileNameFormatChanged(string value) =>
|
||||
NormalizeFileNameFormat(
|
||||
value,
|
||||
trimmed => PostFileNameFormat = trimmed,
|
||||
HandleFileNameFormatChanged(
|
||||
() => PostFileNameFormatError = string.Empty,
|
||||
UpdatePostPreview);
|
||||
|
||||
partial void OnPaidMessageFileNameFormatChanged(string value) =>
|
||||
NormalizeFileNameFormat(
|
||||
value,
|
||||
trimmed => PaidMessageFileNameFormat = trimmed,
|
||||
HandleFileNameFormatChanged(
|
||||
() => PaidMessageFileNameFormatError = string.Empty,
|
||||
UpdatePaidMessagePreview);
|
||||
|
||||
partial void OnMessageFileNameFormatChanged(string value) =>
|
||||
NormalizeFileNameFormat(
|
||||
value,
|
||||
trimmed => MessageFileNameFormat = trimmed,
|
||||
HandleFileNameFormatChanged(
|
||||
() => MessageFileNameFormatError = string.Empty,
|
||||
UpdateMessagePreview);
|
||||
|
||||
@ -341,24 +332,10 @@ public partial class CreatorConfigModalViewModel : ViewModelBase
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
private void NormalizeFileNameFormat(
|
||||
string value,
|
||||
Action<string> setValue,
|
||||
private static void HandleFileNameFormatChanged(
|
||||
Action clearError,
|
||||
Action updatePreview)
|
||||
{
|
||||
if (!_isNormalizingFileNameFormat)
|
||||
{
|
||||
string trimmedValue = value.Trim();
|
||||
if (!string.Equals(value, trimmedValue, StringComparison.Ordinal))
|
||||
{
|
||||
_isNormalizingFileNameFormat = true;
|
||||
setValue(trimmedValue);
|
||||
_isNormalizingFileNameFormat = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
clearError();
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
@ -428,6 +428,11 @@ public partial class MainWindowViewModel(
|
||||
_isUpdatingAllUsersSelected = true;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SelectedListName))
|
||||
{
|
||||
SelectedListName = null;
|
||||
}
|
||||
|
||||
foreach (SelectableUserViewModel user in AvailableUsers)
|
||||
{
|
||||
user.IsSelected = shouldSelectAll;
|
||||
@ -614,7 +619,8 @@ public partial class MainWindowViewModel(
|
||||
{
|
||||
if (!TryBuildConfig(out Config newConfig))
|
||||
{
|
||||
StatusMessage = "Fix configuration validation errors and save again.";
|
||||
ConfigScreenMessage = "Fix configuration validation errors and save again.";
|
||||
StatusMessage = ConfigScreenMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -637,12 +643,12 @@ public partial class MainWindowViewModel(
|
||||
ConfigScreenMessage = "Configuration saved.";
|
||||
StatusMessage = "Configuration saved.";
|
||||
|
||||
if (!await ValidateEnvironmentAsync())
|
||||
if (!await ValidateEnvironmentAsync(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureAuthenticationAndLoadUsersAsync();
|
||||
await EnsureAuthenticationAndLoadUsersAsync(false);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@ -677,7 +683,7 @@ public partial class MainWindowViewModel(
|
||||
|
||||
|
||||
await authService.SaveToFileAsync();
|
||||
bool isAuthValid = await ValidateCurrentAuthAsync();
|
||||
bool isAuthValid = await ValidateCurrentAuthAsync(true);
|
||||
if (!isAuthValid)
|
||||
{
|
||||
AuthScreenMessage = "Authentication is still invalid after login. Please retry.";
|
||||
@ -1041,12 +1047,17 @@ public partial class MainWindowViewModel(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await ValidateEnvironmentAsync())
|
||||
if (!ValidateConfiguredToolPathsOnStartup())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureAuthenticationAndLoadUsersAsync();
|
||||
if (!await ValidateEnvironmentAsync(true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureAuthenticationAndLoadUsersAsync(true);
|
||||
}
|
||||
|
||||
private async Task RunSinglePostOrMessageDownloadAsync(SingleDownloadRequest request)
|
||||
@ -1288,9 +1299,9 @@ public partial class MainWindowViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureAuthenticationAndLoadUsersAsync()
|
||||
private async Task EnsureAuthenticationAndLoadUsersAsync(bool logAuthenticationMessage)
|
||||
{
|
||||
bool hasValidAuth = await TryLoadAndValidateExistingAuthAsync();
|
||||
bool hasValidAuth = await TryLoadAndValidateExistingAuthAsync(logAuthenticationMessage);
|
||||
if (!hasValidAuth)
|
||||
{
|
||||
if (configService.CurrentConfig.DisableBrowserAuth)
|
||||
@ -1310,12 +1321,14 @@ public partial class MainWindowViewModel(
|
||||
await LoadUsersAndListsAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateEnvironmentAsync()
|
||||
private async Task<bool> ValidateEnvironmentAsync(bool logMissingCdmKeysWarning)
|
||||
{
|
||||
SetLoading("Validating environment...");
|
||||
_startupResult = await startupService.ValidateEnvironmentAsync();
|
||||
OnPropertyChanged(nameof(FfmpegVersion));
|
||||
OnPropertyChanged(nameof(FfprobeVersion));
|
||||
FfmpegPathError = string.Empty;
|
||||
FfprobePathError = string.Empty;
|
||||
|
||||
if (!_startupResult.IsWindowsVersionValid)
|
||||
{
|
||||
@ -1325,7 +1338,33 @@ public partial class MainWindowViewModel(
|
||||
|
||||
if (!_startupResult.FfmpegFound)
|
||||
{
|
||||
ConfigScreenMessage = "FFmpeg was not found. Set a valid FFmpegPath before continuing.";
|
||||
FfmpegPathError = BuildToolPathError(
|
||||
nameof(Config.FFmpegPath),
|
||||
configService.CurrentConfig.FFmpegPath,
|
||||
"FFmpeg");
|
||||
ConfigScreenMessage = "FFmpeg was not found. Fix FFmpeg Path and save to continue.";
|
||||
BuildConfigFields(configService.CurrentConfig);
|
||||
FfmpegPathError = BuildToolPathError(
|
||||
nameof(Config.FFmpegPath),
|
||||
configService.CurrentConfig.FFmpegPath,
|
||||
"FFmpeg");
|
||||
CurrentScreen = AppScreen.Config;
|
||||
StatusMessage = ConfigScreenMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_startupResult.FfprobeFound)
|
||||
{
|
||||
FfprobePathError = BuildToolPathError(
|
||||
nameof(Config.FFprobePath),
|
||||
configService.CurrentConfig.FFprobePath,
|
||||
"FFprobe");
|
||||
ConfigScreenMessage = "FFprobe was not found. Fix FFprobe Path and save to continue.";
|
||||
BuildConfigFields(configService.CurrentConfig);
|
||||
FfprobePathError = BuildToolPathError(
|
||||
nameof(Config.FFprobePath),
|
||||
configService.CurrentConfig.FFprobePath,
|
||||
"FFprobe");
|
||||
CurrentScreen = AppScreen.Config;
|
||||
StatusMessage = ConfigScreenMessage;
|
||||
return false;
|
||||
@ -1338,7 +1377,8 @@ public partial class MainWindowViewModel(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_startupResult.ClientIdBlobMissing || _startupResult.DevicePrivateKeyMissing)
|
||||
if (logMissingCdmKeysWarning &&
|
||||
(_startupResult.ClientIdBlobMissing || _startupResult.DevicePrivateKeyMissing))
|
||||
{
|
||||
AppendLog(
|
||||
"CDM key files are missing. Fallback decrypt services will be used for DRM protected videos.");
|
||||
@ -1347,7 +1387,7 @@ public partial class MainWindowViewModel(
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> TryLoadAndValidateExistingAuthAsync()
|
||||
private async Task<bool> TryLoadAndValidateExistingAuthAsync(bool logAuthenticationMessage)
|
||||
{
|
||||
bool loadedFromFile = await authService.LoadFromFileAsync();
|
||||
if (!loadedFromFile)
|
||||
@ -1357,10 +1397,40 @@ public partial class MainWindowViewModel(
|
||||
return false;
|
||||
}
|
||||
|
||||
return await ValidateCurrentAuthAsync();
|
||||
return await ValidateCurrentAuthAsync(logAuthenticationMessage);
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateCurrentAuthAsync()
|
||||
private bool ValidateConfiguredToolPathsOnStartup()
|
||||
{
|
||||
IReadOnlyDictionary<string, string> validationErrors = ConfigValidationService.Validate(configService.CurrentConfig);
|
||||
bool hasToolPathErrors = false;
|
||||
FfmpegPathError = string.Empty;
|
||||
FfprobePathError = string.Empty;
|
||||
|
||||
if (validationErrors.TryGetValue(nameof(Config.FFmpegPath), out string? ffmpegError))
|
||||
{
|
||||
FfmpegPathError = ffmpegError;
|
||||
hasToolPathErrors = true;
|
||||
}
|
||||
|
||||
if (validationErrors.TryGetValue(nameof(Config.FFprobePath), out string? ffprobeError))
|
||||
{
|
||||
FfprobePathError = ffprobeError;
|
||||
hasToolPathErrors = true;
|
||||
}
|
||||
|
||||
if (!hasToolPathErrors)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigScreenMessage = "Configuration has invalid FFmpeg/FFprobe path values. Fix and save to continue.";
|
||||
CurrentScreen = AppScreen.Config;
|
||||
StatusMessage = ConfigScreenMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateCurrentAuthAsync(bool logAuthenticationMessage)
|
||||
{
|
||||
authService.ValidateCookieString();
|
||||
UserEntities.User? user = await authService.ValidateAuthAsync();
|
||||
@ -1383,12 +1453,18 @@ public partial class MainWindowViewModel(
|
||||
if (HidePrivateInfo)
|
||||
{
|
||||
AuthenticatedUserDisplay = "[Hidden for Privacy]";
|
||||
AppendLog("Authenticated as [Hidden for Privacy].");
|
||||
if (logAuthenticationMessage)
|
||||
{
|
||||
AppendLog("Authenticated as [Hidden for Privacy].");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthenticatedUserDisplay = $"{displayName} ({displayUsername})";
|
||||
AppendLog($"Authenticated as {AuthenticatedUserDisplay}.");
|
||||
if (logAuthenticationMessage)
|
||||
{
|
||||
AppendLog($"Authenticated as {AuthenticatedUserDisplay}.");
|
||||
}
|
||||
}
|
||||
|
||||
IsAuthenticated = true;
|
||||
@ -1802,6 +1878,18 @@ public partial class MainWindowViewModel(
|
||||
private static string EscapePathForConfig(string path) =>
|
||||
path.Replace(@"\", @"\\");
|
||||
|
||||
private static string BuildToolPathError(string propertyName, string? configuredPath, string toolName)
|
||||
{
|
||||
string normalizedPath = NormalizePathForDisplay(configuredPath);
|
||||
if (string.IsNullOrWhiteSpace(normalizedPath))
|
||||
{
|
||||
return
|
||||
$"{toolName} was not found automatically. Set {propertyName} to a valid executable path or add {toolName.ToLowerInvariant()} to PATH.";
|
||||
}
|
||||
|
||||
return $"{propertyName} does not point to an existing file: {normalizedPath}";
|
||||
}
|
||||
|
||||
private static string ResolveProgramVersion()
|
||||
{
|
||||
Version? version = Assembly.GetEntryAssembly()?.GetName().Version
|
||||
@ -1916,6 +2004,11 @@ public partial class MainWindowViewModel(
|
||||
{
|
||||
if (e.PropertyName == nameof(SelectableUserViewModel.IsSelected))
|
||||
{
|
||||
if (!_isApplyingListSelection && !string.IsNullOrWhiteSpace(SelectedListName))
|
||||
{
|
||||
SelectedListName = null;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(SelectedUsersSummary));
|
||||
if (!_isUpdatingAllUsersSelected)
|
||||
{
|
||||
|
||||
@ -1065,9 +1065,10 @@
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectableUserViewModel">
|
||||
<CheckBox Content="{Binding Username}"
|
||||
IsChecked="{Binding IsSelected}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<CheckBox IsChecked="{Binding IsSelected}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Username}" />
|
||||
</CheckBox>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user