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 }