Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions 10.0/Apps/DeveloperBalance/AppShell.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
SegmentWidth="40" SegmentHeight="40">
<sf:SfSegmentedControl.ItemsSource>
<x:Array Type="{x:Type sf:SfSegmentItem}">
<sf:SfSegmentItem ImageSource="{StaticResource IconLight}"/>
<sf:SfSegmentItem ImageSource="{StaticResource IconDark}"/>
<sf:SfSegmentItem ImageSource="{StaticResource IconLight}" SemanticProperties.Description="Light mode"/>
<sf:SfSegmentItem ImageSource="{StaticResource IconDark}" SemanticProperties.Description="Dark mode"/>
</x:Array>
</sf:SfSegmentedControl.ItemsSource>
</sf:SfSegmentedControl>
Expand Down
3 changes: 3 additions & 0 deletions 10.0/Apps/DeveloperBalance/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public AppShell()
InitializeComponent();
var currentTheme = Application.Current!.RequestedTheme;
ThemeSegmentedControl.SelectedIndex = currentTheme == AppTheme.Light ? 0 : 1;
#if ANDROID || WINDOWS
SemanticProperties.SetDescription(ThemeSegmentedControl, "Theme selection");
#endif
}
public static async Task DisplaySnackbarAsync(string message)
{
Expand Down
33 changes: 33 additions & 0 deletions 10.0/Apps/DeveloperBalance/Data/TagRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ public async Task<int> 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();

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

/// <summary>
/// Checks if a tag is already associated with a specific project.
/// </summary>
/// <param name="item">The tag to save.</param>
/// <param name="projectID">The ID of the project.</param>
/// <returns>If tag is already associated with this project</returns>
async Task<bool> 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;
}

/// <summary>
/// Deletes a tag from the database.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion 10.0/Apps/DeveloperBalance/DeveloperBalance.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<ApplicationTitle>DeveloperBalance</ApplicationTitle>

<!-- App Identifier -->
<ApplicationId>com.companyname.developerbalance</ApplicationId>
<ApplicationId>com.companyname.developerbalance</ApplicationId>

<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
Expand Down
20 changes: 18 additions & 2 deletions 10.0/Apps/DeveloperBalance/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Syncfusion.Maui.Toolkit.Hosting;

namespace DeveloperBalance;
Expand All @@ -16,9 +18,23 @@ public static MauiApp CreateMauiApp()
.ConfigureMauiHandlers(handlers =>
{
#if IOS || MACCATALYST
handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
#endif
})
#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
})
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
Expand Down
7 changes: 5 additions & 2 deletions 10.0/Apps/DeveloperBalance/PageModels/MainPageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

[RelayCommand]
private Task NavigateToProject(Project project)
=> Shell.Current.GoToAsync($"project?id={project.ID}");
private Task? NavigateToProject(Project project)
=> project is null ? null : Shell.Current.GoToAsync($"project?id={project.ID}");

[RelayCommand]
private Task NavigateToTask(ProjectTask task)
Expand Down
45 changes: 30 additions & 15 deletions 10.0/Apps/DeveloperBalance/PageModels/ProjectDetailPageModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DeveloperBalance.Models;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace DeveloperBalance.PageModels;

Expand Down Expand Up @@ -34,6 +36,8 @@ public partial class ProjectDetailPageModel : ObservableObject, IQueryAttributab
[ObservableProperty]
private List<Tag> _allTags = [];

public IList<object> SelectedTags { get; set; } = new List<object>();

[ObservableProperty]
private IconData _icon;

Expand Down Expand Up @@ -147,6 +151,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);
}
Expand Down Expand Up @@ -186,14 +194,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);
}
}

Expand Down Expand Up @@ -248,7 +253,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;

Expand All @@ -257,20 +262,15 @@ private async Task ToggleTag(Tag tag)
if (tag.IsSelected)
{
await _tagRepository.SaveItemAsync(tag, _project.ID);
AllTags = new(AllTags);
SemanticScreenReader.Announce($"{tag.Title} selected");
}
else
{
await _tagRepository.DeleteItemAsync(tag, _project.ID);
AllTags = new(AllTags);
SemanticScreenReader.Announce($"{tag.Title} unselected");
}
}
else
{
AllTags = new(AllTags);
}

AllTags = new(AllTags);
SemanticScreenReader.Announce($"{tag.Title} {(tag.IsSelected ? "selected" : "unselected")}");
}

[RelayCommand]
Expand All @@ -293,4 +293,19 @@ private async Task CleanTasks()
OnPropertyChanged(nameof(HasCompletedTasks));
await AppShell.DisplayToastAsync("All cleaned up!");
}

[RelayCommand]
private async Task SelectionChanged(object parameter)
{
if (parameter is IEnumerable<object> enumerableParameter)
{
var changed = enumerableParameter.OfType<Tag>().ToList();

if (changed.Count == 0 && SelectedTags is not null)
changed = SelectedTags.OfType<Tag>().Except(enumerableParameter.OfType<Tag>()).ToList();

if (changed.Count == 1)
await ToggleTag(changed[0]);
}
}
}
8 changes: 5 additions & 3 deletions 10.0/Apps/DeveloperBalance/PageModels/ProjectListPageModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#nullable disable
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DeveloperBalance.Data;
Expand All @@ -14,6 +13,9 @@ public partial class ProjectListPageModel : ObservableObject
[ObservableProperty]
private List<Project> _projects = [];

[ObservableProperty]
private Project? selectedProject;

public ProjectListPageModel(ProjectRepository projectRepository)
{
_projectRepository = projectRepository;
Expand All @@ -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()
Expand Down
7 changes: 4 additions & 3 deletions 10.0/Apps/DeveloperBalance/Pages/Controls/CategoryChart.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
HeightRequest="{OnIdiom 300, Phone=200}"
Style="{StaticResource CardStyle}">
<shimmer:SfShimmer
AutomationProperties.IsInAccessibleTree="False"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
VerticalOptions="Fill"
IsActive ="{Binding IsBusy}">
<shimmer:SfShimmer.CustomView>
<Grid>
<BoxView
CornerRadius="12"
VerticalOptions="FillAndExpand"
VerticalOptions="Fill"
Style="{StaticResource ShimmerCustomViewStyle}"/>
</Grid>
</shimmer:SfShimmer.CustomView>
Expand All @@ -38,7 +39,7 @@
<Label Text="{Binding Item.Title}" TextColor="{AppThemeBinding
Light={StaticResource DarkOnLightBackground},
Dark={StaticResource LightOnDarkBackground}}" FontSize="{OnIdiom 18, Phone=14}"/>
<Label Text=" : " TextColor="{AppThemeBinding
<Label Text=": " TextColor="{AppThemeBinding
Light={StaticResource DarkOnLightBackground},
Dark={StaticResource LightOnDarkBackground}}" FontSize="{OnIdiom 18, Phone=14}"/>
<Label Text="{Binding Item.Count}" TextColor="{AppThemeBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
x:DataType="models:Project">
<shimmer:SfShimmer
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
IsActive="{Binding IsBusy, Source={RelativeSource AncestorType={x:Type pageModels:MainPageModel}}, x:DataType=pageModels:IProjectTaskPageModel}">
<shimmer:SfShimmer.CustomView>
<VerticalStackLayout Spacing="15">
Expand Down Expand Up @@ -48,13 +47,13 @@
</Image>
<Label Text="{Binding Name}" TextColor="{AppThemeBinding Light={StaticResource Gray600}, Dark={StaticResource Gray400}}" FontSize="14" TextTransform="Uppercase"/>
<Label Text="{Binding Description}" LineBreakMode="WordWrap"/>
<HorizontalStackLayout Spacing="15" BindableLayout.ItemsSource="{Binding Tags}">
<FlexLayout Wrap="Wrap" Direction="Row" AlignItems="Start" JustifyContent="Start" BindableLayout.ItemsSource="{Binding Tags}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="models:Tag">
<controls:TagView />
</DataTemplate>
</BindableLayout.ItemTemplate>
</HorizontalStackLayout>
</FlexLayout>
</VerticalStackLayout>
</shimmer:SfShimmer.Content>
</shimmer:SfShimmer>
Expand Down
36 changes: 21 additions & 15 deletions 10.0/Apps/DeveloperBalance/Pages/Controls/TaskView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@
x:Class="DeveloperBalance.Pages.Controls.TaskView"
StrokeShape="RoundRectangle 20"
Background="{AppThemeBinding Light={StaticResource LightSecondaryBackground}, Dark={StaticResource DarkSecondaryBackground}}"
SemanticProperties.Description="{Binding Title, StringFormat='{0} Task'}"
x:DataType="models:ProjectTask">

<effectsView:SfEffectsView
TouchDownEffects="Highlight"
HighlightBackground="{AppThemeBinding Light={StaticResource DarkOnLightBackground}, Dark={StaticResource LightOnDarkBackground}}">
<shimmer:SfShimmer
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
VerticalOptions="Fill"
IsActive="{Binding IsBusy, Source={RelativeSource AncestorType={x:Type pageModels:IProjectTaskPageModel}}, x:DataType=pageModels:IProjectTaskPageModel}">
<shimmer:SfShimmer.CustomView>
<Grid
Expand All @@ -36,24 +35,31 @@
</Grid>
</shimmer:SfShimmer.CustomView>
<shimmer:SfShimmer.Content>
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="15" Padding="{OnIdiom 15, Desktop=20}">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding NavigateToTaskCommand, Source={RelativeSource AncestorType={x:Type pageModels:IProjectTaskPageModel}}, x:DataType=pageModels:IProjectTaskPageModel}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>

<Grid ColumnDefinitions="*" ColumnSpacing="15" Padding="{OnIdiom 15, Desktop=20}">

<Grid SemanticProperties.Description="{Binding Title}" Margin="{OnIdiom -15, Desktop=-20}">
<Label
Margin="{OnIdiom '65,0,0,0', Desktop= '70,0,0,0'}"
Text="{Binding Title}"
HorizontalOptions="Start"
VerticalOptions="Center"
LineBreakMode="WordWrap"/>

<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding NavigateToTaskCommand, Source={RelativeSource AncestorType={x:Type pageModels:IProjectTaskPageModel}}, x:DataType=pageModels:IProjectTaskPageModel}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>

<CheckBox Grid.Column="0"
WidthRequest="50"
HorizontalOptions="Start"
IsChecked="{Binding IsCompleted, Mode=OneTime}"
VerticalOptions="Center"
CheckedChanged="CheckBox_CheckedChanged"
AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="{Binding Title}"/>

<Label Grid.Column="1"
Text="{Binding Title}"
VerticalOptions="Center"
LineBreakMode="WordWrap"
AutomationProperties.IsInAccessibleTree="False" />
</Grid>
</shimmer:SfShimmer.Content>
</shimmer:SfShimmer>
Expand Down
Loading
Loading