Browse Source

Dynamically populate file associations view from viewmodel #157

Ruben 7 months ago
parent
commit
ade10f6f36

+ 3 - 0
src/PicView.Avalonia/StartUp/StartUpHelper.cs

@@ -18,6 +18,7 @@ using PicView.Core.Calculations;
 using PicView.Core.Gallery;
 using PicView.Core.Navigation;
 using PicView.Core.ProcessHandling;
+using PicView.Core.ViewModels;
 
 namespace PicView.Avalonia.StartUp;
 
@@ -139,6 +140,8 @@ public static class StartUpHelper
         FileHistory.Initialize();
 
         Application.Current.Name = "PicView";
+        
+        vm.AssociationsViewModel ??= new FileAssociationsViewModel();
     }
 
     private static void HandleThemeUpdates(MainViewModel vm)

+ 7 - 1
src/PicView.Avalonia/Views/FileAssociationsView.axaml

@@ -1,4 +1,5 @@
 <UserControl
+    Focusable="True"
     d:DesignHeight="450"
     d:DesignWidth="800"
     mc:Ignorable="d"
@@ -14,7 +15,10 @@
         <viewModels:MainViewModel />
     </Design.DataContext>
 
-    <StackPanel HorizontalAlignment="Center" Orientation="Vertical">
+    <StackPanel
+        Focusable="True"
+        HorizontalAlignment="Center"
+        Orientation="Vertical">
 
         <TextBlock
             Classes="txt"
@@ -89,6 +93,8 @@
                     DockPanel.Dock="Right"
                     Foreground="{DynamicResource MainTextColorFaded}"
                     HorizontalAlignment="Right"
+                    IconHeight="12"
+                    IconWidth="12"
                     Padding="10,3,10,3"
                     x:Name="ClearButton" />
             </Panel>

+ 231 - 25
src/PicView.Avalonia/Views/FileAssociationsView.axaml.cs

@@ -1,53 +1,259 @@
-using Avalonia.Controls;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
 using PicView.Avalonia.ViewModels;
+using ReactiveUI;
 
 namespace PicView.Avalonia.Views;
 
- public partial class FileAssociationsView : UserControl
+public partial class FileAssociationsView : UserControl
+{
+    private readonly List<(CheckBox CheckBox, string SearchText)> _allCheckBoxes = [];
+    private CompositeDisposable _disposables = new CompositeDisposable();
+        
+    public FileAssociationsView()
     {
-        private readonly List<(CheckBox CheckBox, string SearchText)> _allCheckBoxes = [];
+        InitializeComponent();
+            
+        FilterBox.TextChanged += FilterBox_TextChanged;
+                
+        // Clear button functionality
+        ClearButton.Click += (s, e) => 
+        { 
+            FilterBox.Text = string.Empty;
+            FilterCheckBoxes(string.Empty);
+        };
+            
+        // Setup binding for the buttons
+        SelectAllButton.Click += (s, e) =>
+        {
+            if (DataContext is not MainViewModel vm)
+            {
+                return;
+            }
+
+            vm.AssociationsViewModel.SelectAllCommand.Execute().Subscribe();
+            UpdateCheckBoxesFromViewModel();
+        };
+            
+        UnSelectAllButton.Click += (s, e) =>
+        {
+            if (DataContext is not MainViewModel vm)
+            {
+                return;
+            }
+
+            vm.AssociationsViewModel.UnselectAllCommand.Execute().Subscribe();
+            UpdateCheckBoxesFromViewModel();
+        };
+            
+        DataContextChanged += (s, e) =>
+        {
+            _disposables.Dispose();
+            _disposables = new CompositeDisposable();
+                
+            // Initialize the collection of checkboxes once the DataContext is set
+            InitializeCheckBoxesCollection();
+        };
+    }
         
-        public FileAssociationsView()
+    private void InitializeCheckBoxesCollection()
+    {
+        var container = FileTypesContainer;
+
+        if (DataContext is not MainViewModel vm)
         {
-            InitializeComponent();
+            return;
+        }
             
-            FilterBox.TextChanged += FilterBox_TextChanged;
+        // Subscribe to changes in the filter text
+        vm.AssociationsViewModel.WhenAnyValue(x => x.FilterText)
+            .Subscribe(FilterCheckBoxes)
+            .DisposeWith(_disposables);
+            
+        // Create checkboxes for each file type group and item
+        foreach (var fileTypeGroup in vm.AssociationsViewModel.FileTypeGroups)
+        {
+            // Create group header checkbox
+            var groupCheckBox = new CheckBox
+            {
+                Classes = { "altHover", "y" },
+                Name = $"{fileTypeGroup.Name.Replace(" ", "")}Group", // Remove spaces for the name
+                IsChecked = fileTypeGroup.IsSelected,
+            };
+                
+            var groupTextBlock = new TextBlock
+            {
+                Classes = { "txt" },
+                Text = fileTypeGroup.Name,
+                FontFamily = new FontFamily("avares://PicView.Avalonia/Assets/Fonts/Roboto-Bold.ttf#Roboto")
+            };
+                
+            groupCheckBox.Content = groupTextBlock;
+                
+            // Add to container
+            container.Children.Add(groupCheckBox);
+                
+            // Add to the collection for filtering
+            _allCheckBoxes.Add((groupCheckBox, fileTypeGroup.Name));
                 
-            // Clear button functionality
-            var clearButton = ClearButton;
-            if (clearButton != null)
+            // Handle group checkbox changes to update all items in the group
+            groupCheckBox.IsCheckedChanged += (s, e) =>
             {
-                clearButton.Click += (s, e) => 
-                { 
-                    FilterBox.Text = string.Empty;
-                    FilterCheckBoxes(string.Empty);
+                var isChecked = groupCheckBox.IsChecked;
+                if (!isChecked.HasValue)
+                {
+                    return;
+                }
+
+                foreach (var fileType in fileTypeGroup.FileTypes)
+                {
+                    fileType.IsSelected = isChecked.Value;
+                }
+                UpdateCheckBoxesFromViewModel();
+            };
+                
+            // Create checkboxes for each file type item in the group
+            foreach (var fileType in fileTypeGroup.FileTypes)
+            {
+                var fileCheckBox = new CheckBox
+                {
+                    Classes = { "altHover", "x" },
+                    IsChecked = fileType.IsSelected,
+                };
+                    
+                var fileTextBlock = new TextBlock
+                {
+                    Classes = { "txt" },
+                    Text = $"{fileType.Description} ({fileType.Extension})",
+                    Margin = new Thickness(0),
+                    Padding = new Thickness(0, 1, 5, 0),
+                };
+                    
+                fileCheckBox.Content = fileTextBlock;
+                    
+                // Add to container
+                container.Children.Add(fileCheckBox);
+                    
+                // Add to the collection for filtering
+                _allCheckBoxes.Add((fileCheckBox, $"{fileType.Description} {fileType.Extension}"));
+                    
+                // Bind the checkbox to the file type's IsSelected property
+                fileCheckBox.IsCheckedChanged += (s, e) =>
+                {
+                    if (fileCheckBox.IsChecked.HasValue)
+                    {
+                        fileType.IsSelected = fileCheckBox.IsChecked.Value;
+                    }
                 };
+                    
+                // Subscribe to changes in the file type's IsSelected property
+                fileType.WhenAnyValue(x => x.IsSelected)
+                    .ObserveOn(RxApp.MainThreadScheduler)
+                    .Subscribe(isSelected =>
+                    {
+                        fileCheckBox.IsChecked = isSelected;
+                    })
+                    .DisposeWith(_disposables);
+                    
+                // Subscribe to changes in the file type's IsVisible property
+                fileType.WhenAnyValue(x => x.IsVisible)
+                    .ObserveOn(RxApp.MainThreadScheduler)
+                    .Subscribe(isVisible =>
+                    {
+                        fileCheckBox.IsVisible = isVisible;
+                    })
+                    .DisposeWith(_disposables);
             }
-            
-            // Initialize the collection of checkboxes for filtering
-            InitializeCheckBoxesCollection();
         }
+            
+        // Initial filter
+        //FilterCheckBoxes(vm.AssociationsViewModel.FilterText);
+    }
         
-        private void InitializeCheckBoxesCollection()
+    private void UpdateCheckBoxesFromViewModel()
+    {
+        if (DataContext is not MainViewModel vm)
+            return;
+            
+        foreach (var group in vm.AssociationsViewModel.FileTypeGroups)
         {
-            var container = FileTypesContainer;
+            // Find the group checkbox
+            var groupName = $"{group.Name.Replace(" ", "")}Group";
+            var groupCheckBox = FindLogicalDescendant<CheckBox>(groupName);
+            if (groupCheckBox == null)
+            {
+                continue;
+            }
 
-            if (DataContext is not MainViewModel vm)
+            // Check if all children are selected
+            var allSelected = true;
+            var anySelected = false;
+                    
+            foreach (var fileType in group.FileTypes)
             {
-                return;
+                if (!fileType.IsSelected)
+                    allSelected = false;
+                else
+                    anySelected = true;
             }
-            
-            foreach (var fileTypeGroup in vm.AssociationsViewModel.FileTypeGroups)
+                    
+            groupCheckBox.IsChecked = allSelected ? true : (anySelected ? null : false);
+        }
+    }
+        
+    private T? FindLogicalDescendant<T>(string name) where T : Control
+    {
+        foreach (var child in LogicalChildren)
+        {
+            switch (child)
             {
+                case T control when control.Name == name:
+                    return control;
+                case not null:
+                {
+                    foreach (var grandchild in child.LogicalChildren)
+                    {
+                        if (grandchild is T grandControl && grandControl.Name == name)
+                            return grandControl;
+                    }
+
+                    break;
+                }
             }
         }
+            
+        return null;
+    }
         
-        private void FilterBox_TextChanged(object? sender, EventArgs e)
+    private void FilterBox_TextChanged(object? sender, EventArgs e)
+    {
+        return;
+        if (DataContext is MainViewModel vm)
         {
-            FilterCheckBoxes(FilterBox.Text);
+            vm.AssociationsViewModel.FilterText = FilterBox.Text;
         }
+    }
         
-        private void FilterCheckBoxes(string filterText)
+    private void FilterCheckBoxes(string filterText)
+    {
+        if (string.IsNullOrWhiteSpace(filterText))
+        {
+            // Show all checkboxes
+            foreach (var (checkBox, _) in _allCheckBoxes)
+            {
+                checkBox.IsVisible = true;
+            }
+            return;
+        }
+            
+        filterText = filterText.ToLowerInvariant();
+            
+        foreach (var (checkBox, searchText) in _allCheckBoxes)
         {
+            checkBox.IsVisible = searchText.Contains(filterText, StringComparison.InvariantCultureIgnoreCase);
         }
     }
+}