Skip to content

Commit 6fc6c61

Browse files
Merge branch 'main' into dev/doti/tip-calc
2 parents 2d148c6 + c48572f commit 6fc6c61

20 files changed

+247
-162
lines changed

.github/workflows/build-all.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ jobs:
1616
- name: Checkout code
1717
uses: actions/checkout@v4
1818

19-
- name: Install .NET 9
19+
- name: Install .NET SDKs
2020
uses: actions/setup-dotnet@v4
2121
with:
22-
dotnet-version: 9.0
22+
dotnet-version: |
23+
8.0.x
24+
9.0.x
25+
10.0.x
26+
include-prerelease: true
27+
dotnet-quality: 'preview'
2328

2429
- name: Install .NET MAUI Workload
2530
run: dotnet workload install maui
2631

2732
- name: Select Xcode Version
28-
run: sudo xcode-select -s /Applications/Xcode_16.4.app
33+
run: sudo xcode-select -s /Applications/Xcode_16.3.app
2934
if: runner.os == 'macOS'
3035

3136
- name: Find and build all C# projects

.github/workflows/build-pr.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,27 @@ jobs:
2020
with:
2121
fetch-depth: 2
2222

23-
- name: Install .NET 9
23+
- name: Install .NET SDKs
2424
uses: actions/setup-dotnet@v4
2525
with:
26-
dotnet-version: 9.0
26+
dotnet-version: |
27+
8.0.x
28+
9.0.x
29+
10.0.x
30+
include-prerelease: true
31+
dotnet-quality: 'preview'
2732

2833
- name: Install .NET MAUI Workload
2934
run: dotnet workload install maui
3035

3136
- name: Select Xcode Version
32-
run: sudo xcode-select -s /Applications/Xcode_16.4.app
37+
run: sudo xcode-select -s /Applications/Xcode_16.3.app
3338
if: runner.os == 'macOS'
3439

3540
- name: Find and build changed projects
3641
run: |
3742
$failedProjectCount=0
43+
$skippedProjectCount=0
3844
# Get the list of changed files
3945
$changedFiles = git diff --name-only -r --diff-filter=d HEAD^1 HEAD
4046

9.0/Apps/DeveloperBalance/AppShell.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
SegmentWidth="40" SegmentHeight="40">
3434
<sf:SfSegmentedControl.ItemsSource>
3535
<x:Array Type="{x:Type sf:SfSegmentItem}">
36-
<sf:SfSegmentItem ImageSource="{StaticResource IconLight}"/>
37-
<sf:SfSegmentItem ImageSource="{StaticResource IconDark}"/>
36+
<sf:SfSegmentItem ImageSource="{StaticResource IconLight}" SemanticProperties.Description="Light mode"/>
37+
<sf:SfSegmentItem ImageSource="{StaticResource IconDark}" SemanticProperties.Description="Dark mode"/>
3838
</x:Array>
3939
</sf:SfSegmentedControl.ItemsSource>
4040
</sf:SfSegmentedControl>

9.0/Apps/DeveloperBalance/AppShell.xaml.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public AppShell()
1010
InitializeComponent();
1111
var currentTheme = Application.Current!.RequestedTheme;
1212
ThemeSegmentedControl.SelectedIndex = currentTheme == AppTheme.Light ? 0 : 1;
13+
#if ANDROID || WINDOWS
14+
SemanticProperties.SetDescription(ThemeSegmentedControl, "Theme selection");
15+
#endif
1316
}
1417
public static async Task DisplaySnackbarAsync(string message)
1518
{

9.0/Apps/DeveloperBalance/Data/TagRepository.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ public async Task<int> SaveItemAsync(Tag item, int projectID)
200200
await Init();
201201
await SaveItemAsync(item);
202202

203+
var isAssociated = await IsAssociated(item, projectID);
204+
if (isAssociated)
205+
{
206+
return 0; // No need to save again if already associated
207+
}
208+
203209
await using var connection = new SqliteConnection(Constants.DatabasePath);
204210
await connection.OpenAsync();
205211

@@ -212,6 +218,33 @@ public async Task<int> SaveItemAsync(Tag item, int projectID)
212218
return await saveCmd.ExecuteNonQueryAsync();
213219
}
214220

221+
/// <summary>
222+
/// Checks if a tag is already associated with a specific project.
223+
/// </summary>
224+
/// <param name="item">The tag to save.</param>
225+
/// <param name="projectID">The ID of the project.</param>
226+
/// <returns>If tag is already associated with this project</returns>
227+
async Task<bool> IsAssociated(Tag item, int projectID)
228+
{
229+
await Init();
230+
await SaveItemAsync(item);
231+
232+
await using var connection = new SqliteConnection(Constants.DatabasePath);
233+
await connection.OpenAsync();
234+
235+
// First check if the association already exists
236+
var checkCmd = connection.CreateCommand();
237+
checkCmd.CommandText = @"
238+
SELECT COUNT(*) FROM ProjectsTags
239+
WHERE ProjectID = @projectID AND TagID = @tagID";
240+
checkCmd.Parameters.AddWithValue("@projectID", projectID);
241+
checkCmd.Parameters.AddWithValue("@tagID", item.ID);
242+
243+
int existingCount = Convert.ToInt32(await checkCmd.ExecuteScalarAsync());
244+
245+
return existingCount != 0;
246+
}
247+
215248
/// <summary>
216249
/// Deletes a tag from the database.
217250
/// </summary>

9.0/Apps/DeveloperBalance/DeveloperBalance.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
@@ -21,13 +21,14 @@
2121
<Nullable>enable</Nullable>
2222
<!-- https://github.com/CommunityToolkit/Maui/issues/2205 -->
2323
<NoWarn>XC0103</NoWarn>
24+
<MauiVersion>9.0.60</MauiVersion>
2425
<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
2526

2627
<!-- Display name -->
2728
<ApplicationTitle>DeveloperBalance</ApplicationTitle>
2829

2930
<!-- App Identifier -->
30-
<ApplicationId>com.companyname.developerbalance</ApplicationId>
31+
<ApplicationId>com.companyname.developerbalance</ApplicationId>
3132

3233
<!-- Versions -->
3334
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
@@ -63,7 +64,7 @@
6364
</ItemGroup>
6465

6566
<ItemGroup>
66-
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.40" />
67+
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
6768
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0" />
6869
</ItemGroup>
6970

@@ -72,7 +73,7 @@
7273
<PackageReference Include="CommunityToolkit.Maui" Version="11.1.1" />
7374
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.8" />
7475
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.10" />
75-
<PackageReference Include="Syncfusion.Maui.Toolkit" Version="1.0.4" />
76+
<PackageReference Include="Syncfusion.Maui.Toolkit" Version="1.0.5" />
7677
</ItemGroup>
7778

7879
</Project>

9.0/Apps/DeveloperBalance/MauiProgram.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using CommunityToolkit.Maui;
22
using Microsoft.Extensions.Logging;
3+
using Microsoft.Maui.Handlers;
4+
using Microsoft.Maui.Platform;
35
using Syncfusion.Maui.Toolkit.Hosting;
46

57
namespace DeveloperBalance;
@@ -16,9 +18,23 @@ public static MauiApp CreateMauiApp()
1618
.ConfigureMauiHandlers(handlers =>
1719
{
1820
#if IOS || MACCATALYST
19-
handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
21+
handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
2022
#endif
21-
})
23+
#if WINDOWS
24+
Microsoft.Maui.Controls.Handlers.Items.CollectionViewHandler.Mapper.AppendToMapping("KeyboardAccessibleCollectionView", (handler, view) =>
25+
{
26+
handler.PlatformView.SingleSelectionFollowsFocus = false;
27+
});
28+
29+
Microsoft.Maui.Handlers.ContentViewHandler.Mapper.AppendToMapping(nameof(Pages.Controls.CategoryChart), (handler, view) =>
30+
{
31+
if (view is Pages.Controls.CategoryChart && handler.PlatformView is ContentPanel contentPanel)
32+
{
33+
contentPanel.IsTabStop = true;
34+
}
35+
});
36+
#endif
37+
})
2238
.ConfigureFonts(fonts =>
2339
{
2440
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");

9.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public partial class MainPageModel : ObservableObject, IProjectTaskPageModel
3535
[ObservableProperty]
3636
private string _today = DateTime.Now.ToString("dddd, MMM d");
3737

38+
[ObservableProperty]
39+
private Project? selectedProject;
40+
3841
public bool HasCompletedTasks
3942
=> Tasks?.Any(t => t.IsCompleted) ?? false;
4043

@@ -149,8 +152,8 @@ private Task AddTask()
149152
=> Shell.Current.GoToAsync($"task");
150153

151154
[RelayCommand]
152-
private Task NavigateToProject(Project project)
153-
=> Shell.Current.GoToAsync($"project?id={project.ID}");
155+
private Task? NavigateToProject(Project project)
156+
=> project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}");
154157

155158
[RelayCommand]
156159
private Task NavigateToTask(ProjectTask task)

9.0/Apps/DeveloperBalance/PageModels/ProjectDetailPageModel.cs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using CommunityToolkit.Mvvm.ComponentModel;
22
using CommunityToolkit.Mvvm.Input;
33
using DeveloperBalance.Models;
4+
using System.Collections.ObjectModel;
5+
using System.Windows.Input;
46

57
namespace DeveloperBalance.PageModels;
68

@@ -34,14 +36,16 @@ public partial class ProjectDetailPageModel : ObservableObject, IQueryAttributab
3436
[ObservableProperty]
3537
private List<Tag> _allTags = [];
3638

39+
public IList<object> SelectedTags { get; set; } = new List<object>();
40+
3741
[ObservableProperty]
3842
private IconData _icon;
3943

4044
[ObservableProperty]
4145
bool _isBusy;
4246

4347
[ObservableProperty]
44-
private List<IconData> _icons = new List<IconData>
48+
private List<IconData> _icons = new List<IconData>
4549
{
4650
new IconData { Icon = FluentUI.ribbon_24_regular, Description = "Ribbon Icon" },
4751
new IconData { Icon = FluentUI.ribbon_star_24_regular, Description = "Ribbon Star Icon" },
@@ -135,6 +139,10 @@ private async Task LoadData(int id)
135139
foreach (var tag in allTags)
136140
{
137141
tag.IsSelected = _project.Tags.Any(t => t.ID == tag.ID);
142+
if (tag.IsSelected)
143+
{
144+
SelectedTags.Add(tag);
145+
}
138146
}
139147
AllTags = new(allTags);
140148
}
@@ -173,14 +181,11 @@ private async Task Save()
173181
_project.Icon = Icon.Icon ?? FluentUI.ribbon_24_regular;
174182
await _projectRepository.SaveItemAsync(_project);
175183

176-
if (_project.IsNullOrNew())
184+
foreach (var tag in AllTags)
177185
{
178-
foreach (var tag in AllTags)
186+
if (tag.IsSelected)
179187
{
180-
if (tag.IsSelected)
181-
{
182-
await _tagRepository.SaveItemAsync(tag, _project.ID);
183-
}
188+
await _tagRepository.SaveItemAsync(tag, _project.ID);
184189
}
185190
}
186191

@@ -235,7 +240,7 @@ private Task NavigateToTask(ProjectTask task) =>
235240
Shell.Current.GoToAsync($"task?id={task.ID}");
236241

237242
[RelayCommand]
238-
private async Task ToggleTag(Tag tag)
243+
internal async Task ToggleTag(Tag tag)
239244
{
240245
tag.IsSelected = !tag.IsSelected;
241246

@@ -244,20 +249,15 @@ private async Task ToggleTag(Tag tag)
244249
if (tag.IsSelected)
245250
{
246251
await _tagRepository.SaveItemAsync(tag, _project.ID);
247-
AllTags = new(AllTags);
248-
await AnnouncementHelper.Announce($"{tag.Title} selected");
249252
}
250253
else
251254
{
252255
await _tagRepository.DeleteItemAsync(tag, _project.ID);
253-
AllTags = new(AllTags);
254-
await AnnouncementHelper.Announce($"{tag.Title} unselected");
255256
}
256257
}
257-
else
258-
{
259-
AllTags = new(AllTags);
260-
}
258+
259+
AllTags = new(AllTags);
260+
await AnnouncementHelper.Announce($"{tag.Title} {(tag.IsSelected ? "selected" : "unselected")}");
261261
}
262262

263263
[RelayCommand]
@@ -280,4 +280,19 @@ private async Task CleanTasks()
280280
OnPropertyChanged(nameof(HasCompletedTasks));
281281
await AppShell.DisplayToastAsync("All cleaned up!");
282282
}
283-
}
283+
284+
[RelayCommand]
285+
private async Task SelectionChanged(object parameter)
286+
{
287+
if (parameter is IEnumerable<object> enumerableParameter)
288+
{
289+
var changed = enumerableParameter.OfType<Tag>().ToList();
290+
291+
if (changed.Count == 0 && SelectedTags is not null)
292+
changed = SelectedTags.OfType<Tag>().Except(enumerableParameter.OfType<Tag>()).ToList();
293+
294+
if (changed.Count == 1)
295+
await ToggleTag(changed[0]);
296+
}
297+
}
298+
}

9.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using CommunityToolkit.Mvvm.ComponentModel;
32
using CommunityToolkit.Mvvm.Input;
43
using DeveloperBalance.Data;
@@ -14,6 +13,9 @@ public partial class ProjectListPageModel : ObservableObject
1413
[ObservableProperty]
1514
private List<Project> _projects = [];
1615

16+
[ObservableProperty]
17+
private Project? selectedProject;
18+
1719
public ProjectListPageModel(ProjectRepository projectRepository)
1820
{
1921
_projectRepository = projectRepository;
@@ -26,8 +28,8 @@ private async Task Appearing()
2628
}
2729

2830
[RelayCommand]
29-
Task NavigateToProject(Project project)
30-
=> Shell.Current.GoToAsync($"project?id={project.ID}");
31+
Task? NavigateToProject(Project project)
32+
=> project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}");
3133

3234
[RelayCommand]
3335
async Task AddProject()

0 commit comments

Comments
 (0)