/ src / modules / cmdpal / ext / Microsoft.CmdPal.Ext.Apps / AllAppsPage.cs
AllAppsPage.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.Diagnostics;
  8  using System.Threading;
  9  using System.Threading.Tasks;
 10  using ManagedCommon;
 11  using Microsoft.CmdPal.Ext.Apps.Programs;
 12  using Microsoft.CmdPal.Ext.Apps.Properties;
 13  using Microsoft.CmdPal.Ext.Apps.State;
 14  using Microsoft.CommandPalette.Extensions;
 15  using Microsoft.CommandPalette.Extensions.Toolkit;
 16  
 17  namespace Microsoft.CmdPal.Ext.Apps;
 18  
 19  public sealed partial class AllAppsPage : ListPage
 20  {
 21      private readonly Lock _listLock = new();
 22      private readonly IAppCache _appCache;
 23  
 24      private AppItem[] allApps = [];
 25      private AppListItem[] unpinnedApps = [];
 26      private AppListItem[] pinnedApps = [];
 27  
 28      public AllAppsPage()
 29          : this(AppCache.Instance.Value)
 30      {
 31      }
 32  
 33      public AllAppsPage(IAppCache appCache)
 34      {
 35          _appCache = appCache ?? throw new ArgumentNullException(nameof(appCache));
 36          this.Name = Resources.all_apps;
 37          this.Icon = Icons.AllAppsIcon;
 38          this.ShowDetails = true;
 39          this.IsLoading = true;
 40          this.PlaceholderText = Resources.search_installed_apps_placeholder;
 41  
 42          // Subscribe to pin state changes to refresh the command provider
 43          PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
 44  
 45          Task.Run(() =>
 46          {
 47              lock (_listLock)
 48              {
 49                  BuildListItems();
 50              }
 51          });
 52      }
 53  
 54      internal AppListItem[] GetPinnedApps()
 55      {
 56          BuildListItems();
 57          return pinnedApps;
 58      }
 59  
 60      public override IListItem[] GetItems()
 61      {
 62          // Build or update the list if needed
 63          BuildListItems();
 64  
 65          AppListItem[] allApps = [.. pinnedApps, .. unpinnedApps];
 66          return allApps;
 67      }
 68  
 69      private void BuildListItems()
 70      {
 71          if (allApps.Length == 0 || _appCache.ShouldReload())
 72          {
 73              lock (_listLock)
 74              {
 75                  this.IsLoading = true;
 76  
 77                  Stopwatch stopwatch = new();
 78                  stopwatch.Start();
 79  
 80                  var apps = GetPrograms();
 81                  this.allApps = apps.AllApps;
 82                  this.pinnedApps = apps.PinnedItems;
 83                  this.unpinnedApps = apps.UnpinnedItems;
 84  
 85                  this.IsLoading = false;
 86  
 87                  _appCache.ResetReloadFlag();
 88  
 89                  stopwatch.Stop();
 90                  Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
 91              }
 92          }
 93      }
 94  
 95      private AppItem[] GetAllApps()
 96      {
 97          List<AppItem> allApps = new();
 98  
 99          foreach (var uwpApp in _appCache.UWPs)
100          {
101              if (uwpApp.Enabled)
102              {
103                  allApps.Add(uwpApp.ToAppItem());
104              }
105          }
106  
107          foreach (var win32App in _appCache.Win32s)
108          {
109              if (win32App.Enabled && win32App.Valid)
110              {
111                  allApps.Add(win32App.ToAppItem());
112              }
113          }
114  
115          return [.. allApps];
116      }
117  
118      internal (AppItem[] AllApps, AppListItem[] PinnedItems, AppListItem[] UnpinnedItems) GetPrograms()
119      {
120          var allApps = GetAllApps();
121          var pinned = new List<AppListItem>();
122          var unpinned = new List<AppListItem>();
123  
124          foreach (var app in allApps)
125          {
126              var isPinned = PinnedAppsManager.Instance.IsAppPinned(app.AppIdentifier);
127              var appListItem = new AppListItem(app, true, isPinned);
128  
129              if (isPinned)
130              {
131                  appListItem.Tags = [.. appListItem.Tags, new Tag() { Icon = Icons.PinIcon }];
132                  pinned.Add(appListItem);
133              }
134              else
135              {
136                  unpinned.Add(appListItem);
137              }
138          }
139  
140          pinned.Sort((a, b) => string.Compare(a.Title, b.Title, StringComparison.Ordinal));
141          unpinned.Sort((a, b) => string.Compare(a.Title, b.Title, StringComparison.Ordinal));
142  
143          return (
144                  allApps,
145                  pinned.ToArray(),
146                  unpinned.ToArray()
147          );
148      }
149  
150      private void OnPinStateChanged(object? sender, PinStateChangedEventArgs e)
151      {
152          /*
153           * Rebuilding all the lists is pretty expensive.
154           * So, instead, we'll just compare pinned items to move existing
155           * items between the two lists.
156          */
157          AppItem? existingAppItem = null;
158  
159          foreach (var app in allApps)
160          {
161              if (app.AppIdentifier == e.AppIdentifier)
162              {
163                  existingAppItem = app;
164                  break;
165              }
166          }
167  
168          if (existingAppItem is not null)
169          {
170              var appListItem = new AppListItem(existingAppItem, true, e.IsPinned);
171  
172              var newPinned = new List<AppListItem>(pinnedApps);
173              var newUnpinned = new List<AppListItem>(unpinnedApps);
174  
175              if (e.IsPinned)
176              {
177                  newPinned.Add(appListItem);
178  
179                  foreach (var app in newUnpinned)
180                  {
181                      if (app.AppIdentifier == e.AppIdentifier)
182                      {
183                          newUnpinned.Remove(app);
184                          break;
185                      }
186                  }
187              }
188              else
189              {
190                  newUnpinned.Add(appListItem);
191  
192                  foreach (var app in newPinned)
193                  {
194                      if (app.AppIdentifier == e.AppIdentifier)
195                      {
196                          newPinned.Remove(app);
197                          break;
198                      }
199                  }
200              }
201  
202              pinnedApps = newPinned.ToArray();
203              unpinnedApps = newUnpinned.ToArray();
204          }
205  
206          RaiseItemsChanged(0);
207      }
208  }