/ src / settings-ui / QuickAccess.UI / ViewModels / AllAppsViewModel.cs
AllAppsViewModel.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Collections.Generic;
  7  using System.Collections.ObjectModel;
  8  using System.Linq;
  9  using global::PowerToys.GPOWrapper;
 10  using ManagedCommon;
 11  using Microsoft.PowerToys.QuickAccess.Helpers;
 12  using Microsoft.PowerToys.QuickAccess.Services;
 13  using Microsoft.PowerToys.Settings.UI.Controls;
 14  using Microsoft.PowerToys.Settings.UI.Library;
 15  using Microsoft.PowerToys.Settings.UI.Library.Helpers;
 16  using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
 17  using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
 18  using Microsoft.UI.Dispatching;
 19  using Microsoft.Windows.ApplicationModel.Resources;
 20  
 21  namespace Microsoft.PowerToys.QuickAccess.ViewModels;
 22  
 23  public sealed class AllAppsViewModel : Observable
 24  {
 25      private readonly object _sortLock = new object();
 26      private readonly IQuickAccessCoordinator _coordinator;
 27      private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
 28      private readonly SettingsUtils _settingsUtils;
 29      private readonly ResourceLoader _resourceLoader;
 30      private readonly DispatcherQueue _dispatcherQueue;
 31      private readonly List<FlyoutMenuItem> _allFlyoutMenuItems = new();
 32      private GeneralSettings _generalSettings;
 33  
 34      // Flag to prevent toggle operations during sorting to avoid race conditions.
 35      private bool _isSorting;
 36  
 37      public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
 38  
 39      public DashboardSortOrder DashboardSortOrder
 40      {
 41          get => _generalSettings.DashboardSortOrder;
 42          set
 43          {
 44              if (_generalSettings.DashboardSortOrder != value)
 45              {
 46                  _generalSettings.DashboardSortOrder = value;
 47                  _coordinator.SendSortOrderUpdate(_generalSettings);
 48                  OnPropertyChanged();
 49                  SortFlyoutMenuItems();
 50              }
 51          }
 52      }
 53  
 54      public AllAppsViewModel(IQuickAccessCoordinator coordinator)
 55      {
 56          _coordinator = coordinator;
 57          _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
 58          _settingsUtils = SettingsUtils.Default;
 59          _settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
 60          _generalSettings = _settingsRepository.SettingsConfig;
 61          _settingsRepository.SettingsChanged += OnSettingsChanged;
 62  
 63          _resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
 64          FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
 65  
 66          BuildFlyoutMenuItems();
 67          RefreshFlyoutMenuItems();
 68      }
 69  
 70      private void BuildFlyoutMenuItems()
 71      {
 72          _allFlyoutMenuItems.Clear();
 73          foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
 74          {
 75              if (moduleType == ModuleType.GeneralSettings)
 76              {
 77                  continue;
 78              }
 79  
 80              _allFlyoutMenuItems.Add(new FlyoutMenuItem
 81              {
 82                  Tag = moduleType,
 83                  EnabledChangedCallback = EnabledChangedOnUI,
 84              });
 85          }
 86      }
 87  
 88      private void OnSettingsChanged(GeneralSettings newSettings)
 89      {
 90          _dispatcherQueue.TryEnqueue(() =>
 91          {
 92              _generalSettings = newSettings;
 93              OnPropertyChanged(nameof(DashboardSortOrder));
 94              RefreshFlyoutMenuItems();
 95          });
 96      }
 97  
 98      public void RefreshSettings()
 99      {
100          if (_settingsRepository.ReloadSettings())
101          {
102              OnSettingsChanged(_settingsRepository.SettingsConfig);
103          }
104      }
105  
106      private void RefreshFlyoutMenuItems()
107      {
108          foreach (var item in _allFlyoutMenuItems)
109          {
110              var moduleType = item.Tag;
111              var gpo = Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType);
112              var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
113              var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
114  
115              item.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
116              item.IsLocked = isLocked;
117              item.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
118  
119              if (item.IsEnabled != isEnabled)
120              {
121                  item.UpdateStatus(isEnabled);
122              }
123          }
124  
125          SortFlyoutMenuItems();
126      }
127  
128      private void SortFlyoutMenuItems()
129      {
130          if (_isSorting)
131          {
132              return;
133          }
134  
135          lock (_sortLock)
136          {
137              _isSorting = true;
138              try
139              {
140                  var sortedItems = DashboardSortOrder switch
141                  {
142                      DashboardSortOrder.ByStatus => _allFlyoutMenuItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
143                      _ => _allFlyoutMenuItems.OrderBy(x => x.Label).ToList(),
144                  };
145  
146                  if (FlyoutMenuItems.Count == 0)
147                  {
148                      foreach (var item in sortedItems)
149                      {
150                          FlyoutMenuItems.Add(item);
151                      }
152  
153                      return;
154                  }
155  
156                  for (int i = 0; i < sortedItems.Count; i++)
157                  {
158                      var item = sortedItems[i];
159                      var oldIndex = FlyoutMenuItems.IndexOf(item);
160  
161                      if (oldIndex != -1 && oldIndex != i)
162                      {
163                          FlyoutMenuItems.Move(oldIndex, i);
164                      }
165                  }
166              }
167              finally
168              {
169                  // Use dispatcher to reset flag after UI updates complete
170                  _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
171                  {
172                      _isSorting = false;
173                  });
174              }
175          }
176      }
177  
178      private void EnabledChangedOnUI(ModuleListItem item)
179      {
180          var flyoutItem = (FlyoutMenuItem)item;
181          var isEnabled = flyoutItem.IsEnabled;
182  
183          // Ignore toggle operations during sorting to prevent race conditions.
184          // Revert the toggle state since UI already changed due to TwoWay binding.
185          if (_isSorting)
186          {
187              flyoutItem.UpdateStatus(!isEnabled);
188              return;
189          }
190  
191          _coordinator.UpdateModuleEnabled(flyoutItem.Tag, flyoutItem.IsEnabled);
192          SortFlyoutMenuItems();
193      }
194  }