diff --git a/common/Helpers/WellKnownSettingsKeys.cs b/common/Helpers/WellKnownSettingsKeys.cs index 353cd89937..58c78a3afb 100644 --- a/common/Helpers/WellKnownSettingsKeys.cs +++ b/common/Helpers/WellKnownSettingsKeys.cs @@ -6,4 +6,6 @@ namespace DevHome.Common.Helpers; public class WellKnownSettingsKeys { public const string IsNotFirstRun = "IsNotFirstRun"; + + public const string IsNotFirstDashboardRun = "IsNotFirstDashboardRun"; } diff --git a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs index 4dc908a366..ee73a2f19c 100644 --- a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs +++ b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs @@ -20,6 +20,23 @@ internal sealed class WidgetHelpers public const string WidgetServiceStorePackageId = "9N3RK8ZV2ZR8"; public const string WidgetServicePackageFamilyName = "Microsoft.WidgetsPlatformRuntime_8wekyb3d8bbwe"; + public static readonly string[] DefaultWidgetDefinitionIds = + { + #if CANARY_BUILD + "Microsoft.Windows.DevHome.Canary_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_CPUUsage", + "Microsoft.Windows.DevHome.Canary_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_GPUUsage", + "Microsoft.Windows.DevHome.Canary_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_NetworkUsage", + #elif STABLE_BUILD + "Microsoft.Windows.DevHome_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_CPUUsage", + "Microsoft.Windows.DevHome_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_GPUUsage", + "Microsoft.Windows.DevHome_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_NetworkUsage", + #else + "Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_CPUUsage", + "Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_GPUUsage", + "Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe!App!!CoreWidgetProvider!!System_NetworkUsage", + #endif + }; + public const string DevHomeHostName = "DevHome"; private const double WidgetPxHeightSmall = 146; diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index 54fe811154..9a0b31733c 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -8,7 +8,9 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; using DevHome.Common; +using DevHome.Common.Contracts; using DevHome.Common.Extensions; +using DevHome.Common.Helpers; using DevHome.Dashboard.Controls; using DevHome.Dashboard.Helpers; using DevHome.Dashboard.Services; @@ -21,6 +23,7 @@ using Microsoft.Windows.Widgets; using Microsoft.Windows.Widgets.Hosts; using Windows.System; +using Log = DevHome.Dashboard.Helpers.Log; namespace DevHome.Dashboard.Views; @@ -37,6 +40,7 @@ public partial class DashboardView : ToolPage public static ObservableCollection PinnedWidgets { get; set; } private static Microsoft.UI.Dispatching.DispatcherQueue _dispatcher; + private readonly ILocalSettingsService _localSettingsService; private const string DraggedWidget = "DraggedWidget"; private const string DraggedIndex = "DraggedIndex"; @@ -52,6 +56,7 @@ public DashboardView() PinnedWidgets = new ObservableCollection(); _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + _localSettingsService = Application.Current.GetService(); ActualThemeChanged += OnActualThemeChanged; @@ -119,7 +124,13 @@ private async Task InitializeDashboard() // Cache the widget icons before we display the widgets, since we include the icons in the widgets. await ViewModel.WidgetIconService.CacheAllWidgetIconsAsync(); - await RestorePinnedWidgetsAsync(); + var isFirstDashboardRun = !(await _localSettingsService.ReadSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun)); + if (isFirstDashboardRun) + { + await Application.Current.GetService().SaveSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun, true); + } + + await InitializePinnedWidgetListAsync(isFirstDashboardRun); } else { @@ -141,7 +152,22 @@ private async Task InitializeDashboard() ViewModel.IsLoading = false; } - private async Task RestorePinnedWidgetsAsync() + private async Task InitializePinnedWidgetListAsync(bool isFirstDashboardRun) + { + var hostWidgets = await GetPreviouslyPinnedWidgets(); + + if ((hostWidgets == null || hostWidgets.Length == 0) && isFirstDashboardRun) + { + // If it's the first time the Dashboard has been displayed and we have no other widgets pinned to a + // different version of Dev Home, pin some default widgets. + await PinDefaultWidgetsAsync(); + return; + } + + await RestorePinnedWidgetsAsync(hostWidgets); + } + + private async Task GetPreviouslyPinnedWidgets() { Log.Logger()?.ReportInfo("DashboardView", "Get widgets for current host"); var widgetHost = await ViewModel.WidgetHostingService.GetWidgetHostAsync(); @@ -150,10 +176,16 @@ private async Task RestorePinnedWidgetsAsync() if (hostWidgets == null) { Log.Logger()?.ReportInfo("DashboardView", $"Found 0 widgets for this host"); - return; + return null; } Log.Logger()?.ReportInfo("DashboardView", $"Found {hostWidgets.Length} widgets for this host"); + + return hostWidgets; + } + + private async Task RestorePinnedWidgetsAsync(Widget[] hostWidgets) + { var restoredWidgetsWithPosition = new SortedDictionary(); var restoredWidgetsWithoutPosition = new SortedDictionary(); var numUnorderedWidgets = 0; @@ -172,7 +204,7 @@ private async Task RestorePinnedWidgetsAsync() { // If we have a widget with no state, Dev Home does not consider it a valid widget // and should delete it, rather than letting it run invisibly in the background. - await DeleteAbandonedWidgetAsync(widget, widgetHost); + await DeleteAbandonedWidgetAsync(widget); continue; } @@ -219,8 +251,10 @@ private async Task RestorePinnedWidgetsAsync() } } - private async Task DeleteAbandonedWidgetAsync(Widget widget, WidgetHost widgetHost) + private async Task DeleteAbandonedWidgetAsync(Widget widget) { + var widgetHost = await ViewModel.WidgetHostingService.GetWidgetHostAsync(); + var length = await Task.Run(() => widgetHost!.GetWidgets().Length); Log.Logger()?.ReportInfo("DashboardView", $"Found abandoned widget, try to delete it..."); Log.Logger()?.ReportInfo("DashboardView", $"Before delete, {length} widgets for this host"); @@ -240,6 +274,51 @@ private async Task PlaceWidget(KeyValuePair orderedWidget, int fina await WidgetHelpers.SetPositionCustomStateAsync(widget, finalPlace); } + private async Task PinDefaultWidgetsAsync() + { + var catalog = await ViewModel.WidgetHostingService.GetWidgetCatalogAsync(); + + if (catalog is null) + { + Log.Logger()?.ReportError("AddWidgetDialog", $"Trying to pin default widgets, but WidgetCatalog is null."); + return; + } + + var widgetDefinitions = await Task.Run(() => catalog!.GetWidgetDefinitions().OrderBy(x => x.DisplayTitle)); + foreach (var widgetDefinition in widgetDefinitions) + { + var id = widgetDefinition.Id; + if (WidgetHelpers.DefaultWidgetDefinitionIds.Contains(id)) + { + await PinDefaultWidgetAsync(widgetDefinition); + } + } + } + + private async Task PinDefaultWidgetAsync(WidgetDefinition defaultWidgetDefinition) + { + try + { + // Create widget + var widgetHost = await ViewModel.WidgetHostingService.GetWidgetHostAsync(); + var size = WidgetHelpers.GetDefaultWidgetSize(defaultWidgetDefinition.GetWidgetCapabilities()); + var newWidget = await Task.Run(async () => await widgetHost?.CreateWidgetAsync(defaultWidgetDefinition.Id, size)); + + // Set custom state on new widget. + var position = PinnedWidgets.Count; + var newCustomState = WidgetHelpers.CreateWidgetCustomState(position); + Log.Logger()?.ReportDebug("DashboardView", $"SetCustomState: {newCustomState}"); + await newWidget.SetCustomStateAsync(newCustomState); + + // Put new widget on the Dashboard. + await InsertWidgetInPinnedWidgetsAsync(newWidget, size, position); + } + catch (Exception ex) + { + Log.Logger()?.ReportError("AddWidgetDialog", $"PinDefaultWidget failed: ", ex); + } + } + [RelayCommand] public async Task GoToWidgetsInStoreAsync() {