From dd9a1663ff386bf92d59dd0258ab5870d59ecf41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:06:31 +0000 Subject: [PATCH 1/9] Initial plan From 032560eaf3d45968704894afae1178b9c4de53b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:24:24 +0000 Subject: [PATCH 2/9] Migrate chart data label binding fix to Developer Balance template Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> --- .../Pages/Controls/CategoryChart.xaml | 76 +++++++++++++------ .../Pages/Controls/ChartDataLabelConverter.cs | 28 +++++++ 2 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 src/Templates/src/templates/maui-mobile/Pages/Controls/ChartDataLabelConverter.cs diff --git a/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml b/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml index b5d5f8472a45..cbb1535828e7 100644 --- a/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml +++ b/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml @@ -2,49 +2,75 @@ + x:DataType="pageModels:MainPageModel" + Style="{StaticResource CardStyle}"> + VerticalOptions="Fill" + IsActive="{Binding IsBusy}"> - - - - - - - - - - + + + + + EnableTooltip="False" + x:Name="doughnutSeries" + Radius="{OnIdiom 0.6, Phone=0.5}" + InnerRadius="0.7"> + + + + + + + + + + + + + + + diff --git a/src/Templates/src/templates/maui-mobile/Pages/Controls/ChartDataLabelConverter.cs b/src/Templates/src/templates/maui-mobile/Pages/Controls/ChartDataLabelConverter.cs new file mode 100644 index 000000000000..f9aba252c78b --- /dev/null +++ b/src/Templates/src/templates/maui-mobile/Pages/Controls/ChartDataLabelConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using MauiApp._1.Models; + +namespace MauiApp._1.Pages.Controls; + +public class ChartDataLabelConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is CategoryChartData categoryData && parameter is string parameterValue) + { + return parameterValue?.ToLower() switch + { + "title" => categoryData.Title, + "count" => categoryData.Count.ToString(), + _ => value?.ToString() + }; + } + + return value?.ToString(); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} From b1e51157f8c893150c36c63197bb8dd2602e3395 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:08:02 +0000 Subject: [PATCH 3/9] Integrate all changes from PR #32148 into Developer Balance template Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> --- .../src/templates/maui-mobile/AppShell.xaml | 4 +- .../templates/maui-mobile/AppShell.xaml.cs | 3 + .../maui-mobile/Data/TagRepository.cs | 33 +++++ .../src/templates/maui-mobile/MauiProgram.cs | 18 ++- .../maui-mobile/PageModels/MainPageModel.cs | 3 + .../PageModels/ProjectDetailPageModel.cs | 51 ++++++-- .../PageModels/ProjectListPageModel.cs | 8 +- .../Pages/Controls/CategoryChart.xaml | 7 +- .../Pages/Controls/ProjectCardView.xaml | 9 +- .../maui-mobile/Pages/Controls/TaskView.xaml | 37 +++--- .../maui-mobile/Pages/ProjectDetailPage.xaml | 115 +++++++++--------- .../maui-mobile/Pages/ProjectListPage.xaml | 32 ++--- .../maui-mobile/Pages/ProjectListPage.xaml.cs | 1 + .../maui-mobile/Pages/TaskDetailPage.xaml | 6 +- .../Platforms/MacCatalyst/Info.plist | 2 + .../Resources/Styles/AppStyles.xaml | 4 +- 16 files changed, 220 insertions(+), 113 deletions(-) diff --git a/src/Templates/src/templates/maui-mobile/AppShell.xaml b/src/Templates/src/templates/maui-mobile/AppShell.xaml index 738a02e3c786..f25afd290c43 100644 --- a/src/Templates/src/templates/maui-mobile/AppShell.xaml +++ b/src/Templates/src/templates/maui-mobile/AppShell.xaml @@ -47,8 +47,8 @@ SegmentWidth="40" SegmentHeight="40"> - - + + diff --git a/src/Templates/src/templates/maui-mobile/AppShell.xaml.cs b/src/Templates/src/templates/maui-mobile/AppShell.xaml.cs index 58fb90dc18ef..e88d6057f64a 100644 --- a/src/Templates/src/templates/maui-mobile/AppShell.xaml.cs +++ b/src/Templates/src/templates/maui-mobile/AppShell.xaml.cs @@ -13,6 +13,9 @@ public AppShell() #if (IncludeSampleContent) var currentTheme = Application.Current!.RequestedTheme; ThemeSegmentedControl.SelectedIndex = currentTheme == AppTheme.Light ? 0 : 1; +#endif +#if ANDROID || WINDOWS + SemanticProperties.SetDescription(ThemeSegmentedControl, "Theme selection"); #endif } #if (IncludeSampleContent) diff --git a/src/Templates/src/templates/maui-mobile/Data/TagRepository.cs b/src/Templates/src/templates/maui-mobile/Data/TagRepository.cs index 7f2a5ed4a479..ee87d540ad15 100644 --- a/src/Templates/src/templates/maui-mobile/Data/TagRepository.cs +++ b/src/Templates/src/templates/maui-mobile/Data/TagRepository.cs @@ -200,6 +200,12 @@ public async Task SaveItemAsync(Tag item, int projectID) await Init(); await SaveItemAsync(item); + var isAssociated = await IsAssociated(item, projectID); + if (isAssociated) + { + return 0; // No need to save again if already associated + } + await using var connection = new SqliteConnection(Constants.DatabasePath); await connection.OpenAsync(); @@ -212,6 +218,33 @@ public async Task SaveItemAsync(Tag item, int projectID) return await saveCmd.ExecuteNonQueryAsync(); } + /// + /// Checks if a tag is already associated with a specific project. + /// + /// The tag to save. + /// The ID of the project. + /// If tag is already associated with this project + async Task IsAssociated(Tag item, int projectID) + { + await Init(); + await SaveItemAsync(item); + + await using var connection = new SqliteConnection(Constants.DatabasePath); + await connection.OpenAsync(); + + // First check if the association already exists + var checkCmd = connection.CreateCommand(); + checkCmd.CommandText = @" + SELECT COUNT(*) FROM ProjectsTags + WHERE ProjectID = @projectID AND TagID = @tagID"; + checkCmd.Parameters.AddWithValue("@projectID", projectID); + checkCmd.Parameters.AddWithValue("@tagID", item.ID); + + int existingCount = Convert.ToInt32(await checkCmd.ExecuteScalarAsync()); + + return existingCount != 0; + } + /// /// Deletes a tag from the database. /// diff --git a/src/Templates/src/templates/maui-mobile/MauiProgram.cs b/src/Templates/src/templates/maui-mobile/MauiProgram.cs index 43dd1272843c..7783f18acb05 100644 --- a/src/Templates/src/templates/maui-mobile/MauiProgram.cs +++ b/src/Templates/src/templates/maui-mobile/MauiProgram.cs @@ -18,14 +18,30 @@ public static MauiApp CreateMauiApp() #if (IncludeSampleContent) .UseMauiCommunityToolkit() .ConfigureSyncfusionToolkit() +#endif .ConfigureMauiHandlers(handlers => { +#if WINDOWS + Microsoft.Maui.Controls.Handlers.Items.CollectionViewHandler.Mapper.AppendToMapping("KeyboardAccessibleCollectionView", (handler, view) => + { + handler.PlatformView.SingleSelectionFollowsFocus = false; + }); + + Microsoft.Maui.Handlers.ContentViewHandler.Mapper.AppendToMapping(nameof(Pages.Controls.CategoryChart), (handler, view) => + { + if (view is Pages.Controls.CategoryChart && handler.PlatformView is ContentPanel contentPanel) + { + contentPanel.IsTabStop = true; + } + }); +#endif //-:cnd:noEmit #if IOS || MACCATALYST handlers.AddHandler(); #endif //+:cnd:noEmit - }) + }) +#if (IncludeSampleContent) #endif .ConfigureFonts(fonts => { diff --git a/src/Templates/src/templates/maui-mobile/PageModels/MainPageModel.cs b/src/Templates/src/templates/maui-mobile/PageModels/MainPageModel.cs index b4bd2c6f8c57..533b4ec72069 100644 --- a/src/Templates/src/templates/maui-mobile/PageModels/MainPageModel.cs +++ b/src/Templates/src/templates/maui-mobile/PageModels/MainPageModel.cs @@ -35,6 +35,9 @@ public partial class MainPageModel : ObservableObject, IProjectTaskPageModel [ObservableProperty] private string _today = DateTime.Now.ToString("dddd, MMM d"); + [ObservableProperty] + private Project? selectedProject; + public bool HasCompletedTasks => Tasks?.Any(t => t.IsCompleted) ?? false; diff --git a/src/Templates/src/templates/maui-mobile/PageModels/ProjectDetailPageModel.cs b/src/Templates/src/templates/maui-mobile/PageModels/ProjectDetailPageModel.cs index 3814d4480ca5..c7d8704e8690 100644 --- a/src/Templates/src/templates/maui-mobile/PageModels/ProjectDetailPageModel.cs +++ b/src/Templates/src/templates/maui-mobile/PageModels/ProjectDetailPageModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MauiApp._1.Models; +using System.Collections.ObjectModel; +using System.Windows.Input; namespace MauiApp._1.PageModels; @@ -34,6 +36,8 @@ public partial class ProjectDetailPageModel : ObservableObject, IQueryAttributab [ObservableProperty] private List _allTags = []; + public IList SelectedTags { get; set; } = new List(); + [ObservableProperty] private IconData _icon; @@ -135,6 +139,10 @@ private async Task LoadData(int id) foreach (var tag in allTags) { tag.IsSelected = _project.Tags.Any(t => t.ID == tag.ID); + if (tag.IsSelected) + { + SelectedTags.Add(tag); + } } AllTags = new(allTags); } @@ -156,7 +164,6 @@ private async Task TaskCompleted(ProjectTask task) OnPropertyChanged(nameof(HasCompletedTasks)); } - [RelayCommand] private async Task Save() { @@ -174,14 +181,11 @@ private async Task Save() _project.Icon = Icon.Icon ?? FluentUI.ribbon_24_regular; await _projectRepository.SaveItemAsync(_project); - if (_project.IsNullOrNew()) + foreach (var tag in AllTags) { - foreach (var tag in AllTags) + if (tag.IsSelected) { - if (tag.IsSelected) - { - await _tagRepository.SaveItemAsync(tag, _project.ID); - } + await _tagRepository.SaveItemAsync(tag, _project.ID); } } @@ -236,7 +240,7 @@ private Task NavigateToTask(ProjectTask task) => Shell.Current.GoToAsync($"task?id={task.ID}"); [RelayCommand] - private async Task ToggleTag(Tag tag) + internal async Task ToggleTag(Tag tag) { tag.IsSelected = !tag.IsSelected; @@ -253,6 +257,7 @@ private async Task ToggleTag(Tag tag) } AllTags = new(AllTags); + SemanticScreenReader.Announce($"{tag.Title} {(tag.IsSelected ? "selected" : "unselected")}"); } [RelayCommand] @@ -269,4 +274,34 @@ private async Task CleanTasks() OnPropertyChanged(nameof(HasCompletedTasks)); await AppShell.DisplayToastAsync("All cleaned up!"); } + + [RelayCommand] + private async Task SelectionChanged(object parameter) + { + if (parameter is IEnumerable enumerableParameter) + { + var currentSelection = enumerableParameter.OfType().ToList(); + var previousSelection = AllTags.Where(t => t.IsSelected).ToList(); + + // Handle newly selected tags + foreach (var tag in currentSelection.Except(previousSelection)) + { + tag.IsSelected = true; + if (!_project.IsNullOrNew()) + { + await _tagRepository.SaveItemAsync(tag, _project.ID); + } + } + + // Handle deselected tags + foreach (var tag in previousSelection.Except(currentSelection)) + { + tag.IsSelected = false; + if (!_project.IsNullOrNew()) + { + await _tagRepository.DeleteItemAsync(tag, _project.ID); + } + } + } + } } diff --git a/src/Templates/src/templates/maui-mobile/PageModels/ProjectListPageModel.cs b/src/Templates/src/templates/maui-mobile/PageModels/ProjectListPageModel.cs index b0299b28e958..cb4a5824abbb 100644 --- a/src/Templates/src/templates/maui-mobile/PageModels/ProjectListPageModel.cs +++ b/src/Templates/src/templates/maui-mobile/PageModels/ProjectListPageModel.cs @@ -1,4 +1,3 @@ -#nullable disable using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MauiApp._1.Data; @@ -14,6 +13,9 @@ public partial class ProjectListPageModel : ObservableObject [ObservableProperty] private List _projects = []; + [ObservableProperty] + private Project? selectedProject; + public ProjectListPageModel(ProjectRepository projectRepository) { _projectRepository = projectRepository; @@ -26,8 +28,8 @@ private async Task Appearing() } [RelayCommand] - Task NavigateToProject(Project project) - => Shell.Current.GoToAsync($"project?id={project.ID}"); + Task? NavigateToProject(Project project) + => project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}"); [RelayCommand] async Task AddProject() diff --git a/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml b/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml index cbb1535828e7..7fe8cc115b6a 100644 --- a/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml +++ b/src/Templates/src/templates/maui-mobile/Pages/Controls/CategoryChart.xaml @@ -5,10 +5,11 @@ xmlns:controls="clr-namespace:MauiApp._1.Pages.Controls" xmlns:shimmer="clr-namespace:Syncfusion.Maui.Toolkit.Shimmer;assembly=Syncfusion.Maui.Toolkit" xmlns:pageModels="clr-namespace:MauiApp._1.PageModels" + xmlns:models="clr-namespace:MauiApp._1.Models" x:Class="MauiApp._1.Pages.Controls.CategoryChart" + x:DataType="pageModels:MainPageModel" HeightRequest="{OnIdiom 300, Phone=200}" Margin="0, 12" - x:DataType="pageModels:MainPageModel" Style="{StaticResource CardStyle}"> -