-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #544 from dotnet/add-developerbalance
Add Developer Balance Sample app
- Loading branch information
Showing
88 changed files
with
12,289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.13.35525.253 main | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeveloperBalance", "DeveloperBalance\DeveloperBalance.csproj", "{02E9826F-36BF-5316-B6F5-69DDC6CA1793}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{02E9826F-36BF-5316-B6F5-69DDC6CA1793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{02E9826F-36BF-5316-B6F5-69DDC6CA1793}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{02E9826F-36BF-5316-B6F5-69DDC6CA1793}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{02E9826F-36BF-5316-B6F5-69DDC6CA1793}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {BF39D4F9-C00C-4751-A4B9-A31E47B152C3} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version = "1.0" encoding = "UTF-8" ?> | ||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:local="clr-namespace:DeveloperBalance" | ||
x:Class="DeveloperBalance.App"> | ||
<Application.Resources> | ||
<ResourceDictionary> | ||
<ResourceDictionary.MergedDictionaries> | ||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" /> | ||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" /> | ||
<ResourceDictionary Source="Resources/Styles/AppStyles.xaml" /> | ||
</ResourceDictionary.MergedDictionaries> | ||
</ResourceDictionary> | ||
</Application.Resources> | ||
</Application> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace DeveloperBalance; | ||
|
||
public partial class App : Application | ||
{ | ||
public App() | ||
{ | ||
InitializeComponent(); | ||
} | ||
|
||
protected override Window CreateWindow(IActivationState? activationState) | ||
{ | ||
return new Window(new AppShell()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<Shell | ||
x:Class="DeveloperBalance.AppShell" | ||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:sf="clr-namespace:Syncfusion.Maui.Toolkit.SegmentedControl;assembly=Syncfusion.Maui.Toolkit" | ||
xmlns:pages="clr-namespace:DeveloperBalance.Pages" | ||
Shell.FlyoutBehavior="Flyout" | ||
Title="DeveloperBalance"> | ||
|
||
<ShellContent | ||
Title="Dashboard" | ||
Icon="{StaticResource IconDashboard}" | ||
ContentTemplate="{DataTemplate pages:MainPage}" | ||
Route="main" /> | ||
|
||
<ShellContent | ||
Title="Projects" | ||
Icon="{StaticResource IconProjects}" | ||
ContentTemplate="{DataTemplate pages:ProjectListPage}" | ||
Route="projects" /> | ||
|
||
<ShellContent | ||
Title="Manage Meta" | ||
Icon="{StaticResource IconMeta}" | ||
ContentTemplate="{DataTemplate pages:ManageMetaPage}" | ||
Route="manage" /> | ||
|
||
<Shell.FlyoutFooter> | ||
<Grid Padding="15"> | ||
<sf:SfSegmentedControl x:Name="ThemeSegmentedControl" | ||
VerticalOptions="Center" HorizontalOptions="Center" SelectionChanged="SfSegmentedControl_SelectionChanged" | ||
SegmentWidth="40" SegmentHeight="40"> | ||
<sf:SfSegmentedControl.ItemsSource> | ||
<x:Array Type="{x:Type sf:SfSegmentItem}"> | ||
<sf:SfSegmentItem ImageSource="{StaticResource IconLight}"/> | ||
<sf:SfSegmentItem ImageSource="{StaticResource IconDark}"/> | ||
</x:Array> | ||
</sf:SfSegmentedControl.ItemsSource> | ||
</sf:SfSegmentedControl> | ||
</Grid> | ||
</Shell.FlyoutFooter> | ||
|
||
</Shell> |
49 changes: 49 additions & 0 deletions
49
9.0/Apps/DeveloperBalance/DeveloperBalance/AppShell.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using CommunityToolkit.Maui.Alerts; | ||
using CommunityToolkit.Maui.Core; | ||
using Font = Microsoft.Maui.Font; | ||
namespace DeveloperBalance; | ||
|
||
public partial class AppShell : Shell | ||
{ | ||
public AppShell() | ||
{ | ||
InitializeComponent(); | ||
var currentTheme = Application.Current!.UserAppTheme; | ||
ThemeSegmentedControl.SelectedIndex = currentTheme == AppTheme.Light ? 0 : 1; | ||
} | ||
public static async Task DisplaySnackbarAsync(string message) | ||
{ | ||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); | ||
|
||
var snackbarOptions = new SnackbarOptions | ||
{ | ||
BackgroundColor = Color.FromArgb("#FF3300"), | ||
TextColor = Colors.White, | ||
ActionButtonTextColor = Colors.Yellow, | ||
CornerRadius = new CornerRadius(0), | ||
Font = Font.SystemFontOfSize(18), | ||
ActionButtonFont = Font.SystemFontOfSize(14) | ||
}; | ||
|
||
var snackbar = Snackbar.Make(message, visualOptions: snackbarOptions); | ||
|
||
await snackbar.Show(cancellationTokenSource.Token); | ||
} | ||
|
||
public static async Task DisplayToastAsync(string message) | ||
{ | ||
// Toast is currently not working in MCT on Windows | ||
if (OperatingSystem.IsWindows()) | ||
return; | ||
|
||
var toast = Toast.Make(message, textSize: 18); | ||
|
||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||
await toast.Show(cts.Token); | ||
} | ||
|
||
private void SfSegmentedControl_SelectionChanged(object sender, Syncfusion.Maui.Toolkit.SegmentedControl.SelectionChangedEventArgs e) | ||
{ | ||
Application.Current!.UserAppTheme = e.NewIndex == 0 ? AppTheme.Light : AppTheme.Dark; | ||
} | ||
} |
184 changes: 184 additions & 0 deletions
184
9.0/Apps/DeveloperBalance/DeveloperBalance/Data/CategoryRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
using DeveloperBalance.Models; | ||
using Microsoft.Data.Sqlite; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace DeveloperBalance.Data; | ||
|
||
/// <summary> | ||
/// Repository class for managing categories in the database. | ||
/// </summary> | ||
public class CategoryRepository | ||
{ | ||
private bool _hasBeenInitialized = false; | ||
private readonly ILogger _logger; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="CategoryRepository"/> class. | ||
/// </summary> | ||
/// <param name="logger">The logger instance.</param> | ||
public CategoryRepository(ILogger<CategoryRepository> logger) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the database connection and creates the Category table if it does not exist. | ||
/// </summary> | ||
private async Task Init() | ||
{ | ||
if (_hasBeenInitialized) | ||
return; | ||
|
||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
try | ||
{ | ||
var createTableCmd = connection.CreateCommand(); | ||
createTableCmd.CommandText = @" | ||
CREATE TABLE IF NOT EXISTS Category ( | ||
ID INTEGER PRIMARY KEY AUTOINCREMENT, | ||
Title TEXT NOT NULL, | ||
Color TEXT NOT NULL | ||
);"; | ||
await createTableCmd.ExecuteNonQueryAsync(); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogError(e, "Error creating Category table"); | ||
throw; | ||
} | ||
|
||
_hasBeenInitialized = true; | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves a list of all categories from the database. | ||
/// </summary> | ||
/// <returns>A list of <see cref="Category"/> objects.</returns> | ||
public async Task<List<Category>> ListAsync() | ||
{ | ||
await Init(); | ||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
var selectCmd = connection.CreateCommand(); | ||
selectCmd.CommandText = "SELECT * FROM Category"; | ||
var categories = new List<Category>(); | ||
|
||
await using var reader = await selectCmd.ExecuteReaderAsync(); | ||
while (await reader.ReadAsync()) | ||
{ | ||
categories.Add(new Category | ||
{ | ||
ID = reader.GetInt32(0), | ||
Title = reader.GetString(1), | ||
Color = reader.GetString(2) | ||
}); | ||
} | ||
|
||
return categories; | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves a specific category by its ID. | ||
/// </summary> | ||
/// <param name="id">The ID of the category.</param> | ||
/// <returns>A <see cref="Category"/> object if found; otherwise, null.</returns> | ||
public async Task<Category?> GetAsync(int id) | ||
{ | ||
await Init(); | ||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
var selectCmd = connection.CreateCommand(); | ||
selectCmd.CommandText = "SELECT * FROM Category WHERE ID = @id"; | ||
selectCmd.Parameters.AddWithValue("@id", id); | ||
|
||
await using var reader = await selectCmd.ExecuteReaderAsync(); | ||
if (await reader.ReadAsync()) | ||
{ | ||
return new Category | ||
{ | ||
ID = reader.GetInt32(0), | ||
Title = reader.GetString(1), | ||
Color = reader.GetString(2) | ||
}; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Saves a category to the database. If the category ID is 0, a new category is created; otherwise, the existing category is updated. | ||
/// </summary> | ||
/// <param name="item">The category to save.</param> | ||
/// <returns>The ID of the saved category.</returns> | ||
public async Task<int> SaveItemAsync(Category item) | ||
{ | ||
await Init(); | ||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
var saveCmd = connection.CreateCommand(); | ||
if (item.ID == 0) | ||
{ | ||
saveCmd.CommandText = @" | ||
INSERT INTO Category (Title, Color) | ||
VALUES (@Title, @Color); | ||
SELECT last_insert_rowid();"; | ||
} | ||
else | ||
{ | ||
saveCmd.CommandText = @" | ||
UPDATE Category SET Title = @Title, Color = @Color | ||
WHERE ID = @ID"; | ||
saveCmd.Parameters.AddWithValue("@ID", item.ID); | ||
} | ||
|
||
saveCmd.Parameters.AddWithValue("@Title", item.Title); | ||
saveCmd.Parameters.AddWithValue("@Color", item.Color); | ||
|
||
var result = await saveCmd.ExecuteScalarAsync(); | ||
if (item.ID == 0) | ||
{ | ||
item.ID = Convert.ToInt32(result); | ||
} | ||
|
||
return item.ID; | ||
} | ||
|
||
/// <summary> | ||
/// Deletes a category from the database. | ||
/// </summary> | ||
/// <param name="item">The category to delete.</param> | ||
/// <returns>The number of rows affected.</returns> | ||
public async Task<int> DeleteItemAsync(Category item) | ||
{ | ||
await Init(); | ||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
var deleteCmd = connection.CreateCommand(); | ||
deleteCmd.CommandText = "DELETE FROM Category WHERE ID = @id"; | ||
deleteCmd.Parameters.AddWithValue("@id", item.ID); | ||
|
||
return await deleteCmd.ExecuteNonQueryAsync(); | ||
} | ||
|
||
/// <summary> | ||
/// Drops the Category table from the database. | ||
/// </summary> | ||
public async Task DropTableAsync() | ||
{ | ||
await Init(); | ||
await using var connection = new SqliteConnection(Constants.DatabasePath); | ||
await connection.OpenAsync(); | ||
|
||
var dropTableCmd = connection.CreateCommand(); | ||
dropTableCmd.CommandText = "DROP TABLE IF EXISTS Category"; | ||
|
||
await dropTableCmd.ExecuteNonQueryAsync(); | ||
_hasBeenInitialized = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace DeveloperBalance.Data; | ||
|
||
public static class Constants | ||
{ | ||
public const string DatabaseFilename = "AppSQLite.db3"; | ||
|
||
public static string DatabasePath => | ||
$"Data Source={Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename)}"; | ||
} |
11 changes: 11 additions & 0 deletions
11
9.0/Apps/DeveloperBalance/DeveloperBalance/Data/JsonContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System.Text.Json.Serialization; | ||
using DeveloperBalance.Models; | ||
|
||
[JsonSerializable(typeof(Project))] | ||
[JsonSerializable(typeof(ProjectTask))] | ||
[JsonSerializable(typeof(ProjectsJson))] | ||
[JsonSerializable(typeof(Category))] | ||
[JsonSerializable(typeof(Tag))] | ||
public partial class JsonContext : JsonSerializerContext | ||
{ | ||
} |
Oops, something went wrong.