forked from sim0n00ps/OF-DL
Add a notification modal when some users could not be selected from a list
This commit is contained in:
parent
d0de99a00c
commit
0af7066086
@ -294,8 +294,9 @@ public class Program(IServiceProvider serviceProvider)
|
||||
}
|
||||
else if (config.NonInteractiveMode && !string.IsNullOrEmpty(config.NonInteractiveModeListName))
|
||||
{
|
||||
Dictionary<string, long> selectedUsers =
|
||||
ListUserSelectionResult listSelectionResult =
|
||||
await orchestrationService.GetUsersForListAsync(config.NonInteractiveModeListName, users, lists);
|
||||
Dictionary<string, long> selectedUsers = listSelectionResult.SelectedUsers;
|
||||
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true, selectedUsers);
|
||||
}
|
||||
else
|
||||
|
||||
@ -27,3 +27,10 @@ public class UserListResult
|
||||
|
||||
public string? IgnoredListError { get; set; }
|
||||
}
|
||||
|
||||
public class ListUserSelectionResult
|
||||
{
|
||||
public Dictionary<string, long> SelectedUsers { get; set; } = new();
|
||||
|
||||
public List<string> UnavailableUsernames { get; set; } = [];
|
||||
}
|
||||
|
||||
@ -110,13 +110,26 @@ public class DownloadOrchestrationService(
|
||||
/// <param name="allUsers">All available users.</param>
|
||||
/// <param name="lists">Known lists keyed by name.</param>
|
||||
/// <returns>The users that belong to the list.</returns>
|
||||
public async Task<Dictionary<string, long>> GetUsersForListAsync(
|
||||
public async Task<ListUserSelectionResult> GetUsersForListAsync(
|
||||
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists)
|
||||
{
|
||||
ListUserSelectionResult result = new();
|
||||
long listId = lists[listName];
|
||||
List<string> listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? [];
|
||||
return allUsers.Where(x => listUsernames.Contains(x.Key))
|
||||
HashSet<string> listUsernamesSet = listUsernames.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> allUsernamesSet = allUsers.Keys.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
result.SelectedUsers = allUsers
|
||||
.Where(x => listUsernamesSet.Contains(x.Key))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
result.UnavailableUsernames = listUsernames
|
||||
.Where(username => !allUsernamesSet.Contains(username))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(username => username, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -17,7 +17,7 @@ public interface IDownloadOrchestrationService
|
||||
/// <summary>
|
||||
/// Get users for a specific list by name.
|
||||
/// </summary>
|
||||
Task<Dictionary<string, long>> GetUsersForListAsync(
|
||||
Task<ListUserSelectionResult> GetUsersForListAsync(
|
||||
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -371,10 +371,23 @@ public partial class MainWindowViewModel(
|
||||
[NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))]
|
||||
private bool _isDownloadSelectionWarningModalOpen;
|
||||
|
||||
[ObservableProperty] private string _downloadSelectionWarningMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(OpenSinglePostOrMessageModalCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))]
|
||||
private bool _isListSelectionWarningModalOpen;
|
||||
|
||||
[ObservableProperty] private string _listSelectionWarningMessage = string.Empty;
|
||||
|
||||
public ObservableCollection<string> ListSelectionUnavailableUsers { get; } = [];
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||
private string _singlePostOrMessageUrl = string.Empty;
|
||||
|
||||
@ -827,20 +840,24 @@ public partial class MainWindowViewModel(
|
||||
user.IsSelected = false;
|
||||
}
|
||||
|
||||
Dictionary<string, long> listUsers = await downloadOrchestrationService.GetUsersForListAsync(
|
||||
ListUserSelectionResult listSelectionResult = await downloadOrchestrationService.GetUsersForListAsync(
|
||||
SelectedListName,
|
||||
_allUsers,
|
||||
_allLists);
|
||||
HashSet<string> selectedUsernames = listUsers.Keys.ToHashSet(StringComparer.Ordinal);
|
||||
HashSet<string> selectedUsernames = listSelectionResult.SelectedUsers.Keys
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (SelectableUserViewModel user in AvailableUsers)
|
||||
{
|
||||
user.IsSelected = selectedUsernames.Contains(user.Username);
|
||||
}
|
||||
|
||||
ShowListSelectionWarningIfNeeded(listSelectionResult.UnavailableUsernames);
|
||||
|
||||
OnPropertyChanged(nameof(SelectedUsersSummary));
|
||||
OnPropertyChanged(nameof(AllUsersSelected));
|
||||
DownloadSelectedCommand.NotifyCanExecuteChanged();
|
||||
SelectUsersFromListCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -935,6 +952,13 @@ public partial class MainWindowViewModel(
|
||||
EditConfig();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CloseListSelectionWarning()
|
||||
{
|
||||
IsListSelectionWarningModalOpen = false;
|
||||
ListSelectionUnavailableUsers.Clear();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanStopWork))]
|
||||
private void StopWork()
|
||||
{
|
||||
@ -1075,6 +1099,7 @@ public partial class MainWindowViewModel(
|
||||
private bool CanApplySelectedList() =>
|
||||
CurrentScreen == AppScreen.UserSelection &&
|
||||
!string.IsNullOrWhiteSpace(SelectedListName) &&
|
||||
!IsListSelectionWarningModalOpen &&
|
||||
!IsDownloading;
|
||||
|
||||
private bool CanDownloadSelected() =>
|
||||
@ -1083,6 +1108,7 @@ public partial class MainWindowViewModel(
|
||||
!IsDownloadSelectionWarningModalOpen &&
|
||||
!IsMissingCdmWarningModalOpen &&
|
||||
!IsShowScrapeSizeWarningModalOpen &&
|
||||
!IsListSelectionWarningModalOpen &&
|
||||
!IsDownloading;
|
||||
|
||||
private bool CanDownloadPurchasedTab() =>
|
||||
@ -1091,6 +1117,7 @@ public partial class MainWindowViewModel(
|
||||
!IsDownloadSelectionWarningModalOpen &&
|
||||
!IsMissingCdmWarningModalOpen &&
|
||||
!IsShowScrapeSizeWarningModalOpen &&
|
||||
!IsListSelectionWarningModalOpen &&
|
||||
!IsDownloading;
|
||||
|
||||
private bool CanOpenSinglePostOrMessageModal() =>
|
||||
@ -1099,6 +1126,7 @@ public partial class MainWindowViewModel(
|
||||
!IsDownloadSelectionWarningModalOpen &&
|
||||
!IsMissingCdmWarningModalOpen &&
|
||||
!IsShowScrapeSizeWarningModalOpen &&
|
||||
!IsListSelectionWarningModalOpen &&
|
||||
!IsSinglePostOrMessageModalOpen;
|
||||
|
||||
private bool CanSubmitSinglePostOrMessage() =>
|
||||
@ -1106,6 +1134,7 @@ public partial class MainWindowViewModel(
|
||||
!IsDownloadSelectionWarningModalOpen &&
|
||||
!IsMissingCdmWarningModalOpen &&
|
||||
!IsShowScrapeSizeWarningModalOpen &&
|
||||
!IsListSelectionWarningModalOpen &&
|
||||
!IsDownloading &&
|
||||
!string.IsNullOrWhiteSpace(SinglePostOrMessageUrl);
|
||||
|
||||
@ -1195,7 +1224,8 @@ public partial class MainWindowViewModel(
|
||||
partial void OnSelectedListNameChanged(string? value)
|
||||
{
|
||||
SelectUsersFromListCommand.NotifyCanExecuteChanged();
|
||||
if (_isApplyingListSelection || IsDownloading || CurrentScreen != AppScreen.UserSelection ||
|
||||
if (_isApplyingListSelection || IsDownloading || IsListSelectionWarningModalOpen ||
|
||||
CurrentScreen != AppScreen.UserSelection ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return;
|
||||
@ -1533,6 +1563,42 @@ public partial class MainWindowViewModel(
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
private void ShowListSelectionWarningIfNeeded(IReadOnlyCollection<string> unavailableUsers)
|
||||
{
|
||||
if (unavailableUsers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ListSelectionUnavailableUsers.Clear();
|
||||
foreach (string username in unavailableUsers.OrderBy(name => name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ListSelectionUnavailableUsers.Add(username);
|
||||
}
|
||||
|
||||
List<string> recommendations = [];
|
||||
if (!configService.CurrentConfig.IncludeExpiredSubscriptions)
|
||||
{
|
||||
recommendations.Add("Include Expired Subscriptions");
|
||||
}
|
||||
|
||||
if (!configService.CurrentConfig.IncludeRestrictedSubscriptions)
|
||||
{
|
||||
recommendations.Add("Include Restricted Subscriptions");
|
||||
}
|
||||
|
||||
string recommendationText = recommendations.Count > 0
|
||||
? $"Recommendation:\nEnable {string.Join(" and ", recommendations.Select(option => $"\"{option}\""))} in the subscriptions section of the configuration page. After doing so, select \"Refresh\" from the file menu."
|
||||
: "These users may be unavailable or inaccessible with your current account.";
|
||||
|
||||
ListSelectionWarningMessage =
|
||||
$"{unavailableUsers.Count} user(s) from \"{SelectedListName}\" could not be selected.\n\n" +
|
||||
$"{recommendationText}\n\n" +
|
||||
"Users that could not be selected:";
|
||||
|
||||
IsListSelectionWarningModalOpen = true;
|
||||
}
|
||||
|
||||
private static bool TryParseSinglePostOrMessageUrl(
|
||||
string url,
|
||||
out SingleDownloadRequest request,
|
||||
|
||||
@ -1573,6 +1573,58 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="0" Grid.RowSpan="3"
|
||||
IsVisible="{Binding IsListSelectionWarningModalOpen}"
|
||||
Background="{DynamicResource OverlayBackgroundBrush}"
|
||||
ZIndex="1002"
|
||||
PointerPressed="OnModalOverlayClicked">
|
||||
<Border Background="{DynamicResource ModalBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource ModalBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="28"
|
||||
Width="720"
|
||||
MaxHeight="560"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
BoxShadow="0 20 25 -5 #19000000, 0 10 10 -5 #0F000000"
|
||||
PointerPressed="OnModalContentClicked">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock FontSize="20"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}"
|
||||
Text="Some Users Could Not Be Selected" />
|
||||
|
||||
<TextBlock Foreground="{DynamicResource TextSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding ListSelectionWarningMessage}" />
|
||||
|
||||
<Border BorderBrush="{DynamicResource SurfaceBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Background="{DynamicResource SurfaceBackgroundBrush}"
|
||||
Padding="8"
|
||||
MaxHeight="240">
|
||||
<ListBox ItemsSource="{Binding ListSelectionUnavailableUsers}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<Button Content="Close"
|
||||
Classes="primary"
|
||||
Command="{Binding CloseListSelectionWarningCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="0" Grid.RowSpan="3"
|
||||
IsVisible="{Binding IsMissingCdmWarningModalOpen}"
|
||||
Background="{DynamicResource OverlayBackgroundBrush}"
|
||||
|
||||
@ -283,6 +283,7 @@ public partial class MainWindow : Window
|
||||
vm.CreatorConfigEditor.ModalViewModel.CancelCommand.Execute(null);
|
||||
vm.CancelSinglePostOrMessageCommand.Execute(null);
|
||||
vm.CancelDownloadSelectionWarningCommand.Execute(null);
|
||||
vm.CloseListSelectionWarningCommand.Execute(null);
|
||||
vm.CancelMissingCdmWarningCommand.Execute(null);
|
||||
vm.CancelShowScrapeSizeWarningCommand.Execute(null);
|
||||
}
|
||||
|
||||
@ -79,10 +79,30 @@ public class DownloadOrchestrationServiceTests
|
||||
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
||||
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
||||
|
||||
Dictionary<string, long> result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
||||
ListUserSelectionResult result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(2, result["bob"]);
|
||||
Assert.Single(result.SelectedUsers);
|
||||
Assert.Equal(2, result.SelectedUsers["bob"]);
|
||||
Assert.Empty(result.UnavailableUsernames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUsersForListAsync_ReturnsUnavailableUsers()
|
||||
{
|
||||
FakeConfigService configService = new(CreateConfig());
|
||||
ConfigurableApiService apiService = new()
|
||||
{
|
||||
ListUsersHandler = _ => Task.FromResult<List<string>?>(["bob", "carol"])
|
||||
};
|
||||
DownloadOrchestrationService service =
|
||||
new(apiService, configService, new OrchestrationDownloadServiceStub(), new UserTrackingDbService());
|
||||
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
||||
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
||||
|
||||
ListUserSelectionResult result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
||||
|
||||
Assert.Single(result.SelectedUsers);
|
||||
Assert.Equal("carol", Assert.Single(result.UnavailableUsernames));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user