budget-view, dashboard-view displayed correct, transactions-form finalized
@@ -13,16 +13,13 @@
|
||||
<RootNamespace>Clario.Android</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Icon.png">
|
||||
<Link>Resources\drawable\Icon.png</Link>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Android"/>
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||
<PackageReference Include="Supabase" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen"/>
|
||||
</ItemGroup>
|
||||
@@ -30,4 +27,9 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Clario\Clario.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\drawable-night-v31\" />
|
||||
<Folder Include="Resources\drawable-v31\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<group
|
||||
android:name="wrapper"
|
||||
android:translateX="21"
|
||||
android:translateY="21">
|
||||
<group android:name="group">
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
|
||||
android:strokeWidth="1"/>
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_2"
|
||||
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
|
||||
android:strokeWidth="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:duration="1000"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#161c2d"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="path_1">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:duration="1000"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#f9f9fb"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="path_2">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:duration="1000"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#f9f9fb"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -1,71 +0,0 @@
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<group
|
||||
android:name="wrapper"
|
||||
android:translateX="21"
|
||||
android:translateY="21">
|
||||
<group android:name="group">
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z"
|
||||
android:fillColor="#00ffffff"
|
||||
android:strokeWidth="1"/>
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData="M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z"
|
||||
android:fillColor="#00ffffff"
|
||||
android:strokeWidth="1"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_2"
|
||||
android:pathData="M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z"
|
||||
android:fillColor="#00ffffff"
|
||||
android:strokeWidth="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="path_2">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:startOffset="100"
|
||||
android:duration="900"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#161c2d"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:duration="500"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#f9f9fb"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="path_1">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="fillColor"
|
||||
android:startOffset="100"
|
||||
android:duration="900"
|
||||
android:valueFrom="#00ffffff"
|
||||
android:valueTo="#161c2d"
|
||||
android:valueType="colorType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
BIN
Clario.Android/Resources/drawable/Icon.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
@@ -5,7 +5,7 @@
|
||||
<color android:color="@color/splash_background"/>
|
||||
</item>
|
||||
|
||||
<item android:drawable="@drawable/icon"
|
||||
<item android:drawable="@drawable/Icon"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:gravity="center" />
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
BIN
Clario.Android/Resources/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
Clario.Android/Resources/mipmap-hdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 844 B |
BIN
Clario.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Clario.Android/Resources/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Clario.Android/Resources/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Clario.Android/Resources/mipmap-mdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 450 B |
BIN
Clario.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Clario.Android/Resources/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Clario.Android/Resources/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
Clario.Android/Resources/mipmap-xhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Clario.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Clario.Android/Resources/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Clario.Android/Resources/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
Clario.Android/Resources/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -9,7 +9,6 @@
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avalonia_anim</item>
|
||||
<item name="android:windowSplashScreenAnimationDuration">1000</item>
|
||||
<item name="postSplashScreenTheme">@style/MyTheme.Main</item>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="splash_background">#FFFFFF</color>
|
||||
<color name="splash_background">#0B0D12</color>
|
||||
</resources>
|
||||
|
||||
BIN
Clario.Android/play_store_512.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -9,7 +9,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Browser"/>
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||
<PackageReference Include="Supabase" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<!--If you are willing to use platform-specific APIs, use conditional compilation.
|
||||
See https://docs.avaloniaui.net/docs/guides/platforms/platform-specific-code/dotnet for more details.-->
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
@@ -19,7 +17,10 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||
<PackageReference Include="Supabase" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Clario.Services;
|
||||
|
||||
namespace Clario.Desktop;
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.iOS"/>
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||
<PackageReference Include="Supabase" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Clario"
|
||||
xmlns:converters="clr-namespace:Clario.Converters"
|
||||
xmlns:views="clr-namespace:Clario.Views"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
|
||||
x:Class="Clario.App"
|
||||
RequestedThemeVariant="Dark">
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator />
|
||||
</Application.DataTemplates>
|
||||
@@ -24,9 +26,12 @@
|
||||
<converters:DecimalSignConverter x:Key="DecimalSignConverter" />
|
||||
<converters:PercentageConverter x:Key="PercentageConverter" />
|
||||
<converters:DecimalColorConverter x:Key="DecimalColorConverter" />
|
||||
<converters:BoolToColorConverter x:Key="BoolToColorConverter" />
|
||||
<converters:BoolToCssConverter x:Key="BoolToCssConverter" />
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="../Theme/AppTheme.axaml" />
|
||||
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@@ -2,11 +2,10 @@ using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using Clario.Data;
|
||||
using Clario.Services;
|
||||
using Clario.ViewModels;
|
||||
@@ -16,15 +15,42 @@ namespace Clario;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public static bool IsMobile { get; private set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
RequestedThemeVariant = ThemeVariant.Dark;
|
||||
}
|
||||
|
||||
public override async void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLoading)
|
||||
{
|
||||
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
|
||||
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
||||
DisableAvaloniaDataAnnotationValidation();
|
||||
|
||||
desktopLoading.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new LoadingViewModel()
|
||||
};
|
||||
desktopLoading.MainWindow.Show();
|
||||
}
|
||||
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatformLoading)
|
||||
{
|
||||
Console.WriteLine("ANDROID PATH HIT");
|
||||
singleViewPlatformLoading.MainView = new MainAppMobile()
|
||||
{
|
||||
DataContext = new LoadingViewModel()
|
||||
};
|
||||
}
|
||||
|
||||
IsMobile = ApplicationLifetime is ISingleViewApplicationLifetime;
|
||||
|
||||
var culture = new CultureInfo("en-US");
|
||||
|
||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||
@@ -36,7 +62,6 @@ public partial class App : Application
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* session invalid or expired */
|
||||
}
|
||||
|
||||
var user = SupabaseService.Client.Auth.CurrentUser;
|
||||
@@ -53,19 +78,13 @@ public partial class App : Application
|
||||
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
||||
DisableAvaloniaDataAnnotationValidation();
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = user is not null ? new MainViewModel() : new AuthViewModel()
|
||||
};
|
||||
desktop.MainWindow.Show();
|
||||
desktop.MainWindow!.DataContext = user is not null ? new MainViewModel() : new AuthViewModel();
|
||||
}
|
||||
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||
{
|
||||
singleViewPlatform.MainView = new MainView
|
||||
{
|
||||
DataContext = user is not null ? new MainViewModel() : new AuthViewModel()
|
||||
};
|
||||
Console.WriteLine("ANDROID PATH HIT");
|
||||
singleViewPlatform.MainView!.DataContext = user is not null ? new MainViewModel() : new AuthViewModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
Clario/Assets/Icons/arrow-left-right.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left-right-icon lucide-arrow-left-right"><path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/></svg>
|
||||
|
After Width: | Height: | Size: 344 B |
1
Clario/Assets/Icons/sliders-horizontal.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sliders-horizontal-icon lucide-sliders-horizontal"><path d="M10 5H3"/><path d="M12 19H3"/><path d="M14 3v4"/><path d="M16 17v4"/><path d="M21 12h-9"/><path d="M21 19h-5"/><path d="M21 5h-7"/><path d="M8 10v4"/><path d="M8 12H3"/></svg>
|
||||
|
After Width: | Height: | Size: 437 B |
BIN
Clario/Assets/Logo.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
@@ -22,7 +22,17 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm"/>
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||
<PackageReference Include="Supabase" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="MobileViews\MainAppMobile.axaml.cs">
|
||||
<DependentUpon>MobileMainView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
30
Clario/Clario.parcel
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"GeneralSettings": {
|
||||
"NetProjectPath": "Clario.csproj",
|
||||
"ApplicationName": "Clario",
|
||||
"Version": "1.0.0",
|
||||
"PackageName": {
|
||||
"$type": "msbuild",
|
||||
"property": "AssemblyName"
|
||||
},
|
||||
"AssemblyName": {
|
||||
"$type": "msbuild",
|
||||
"property": "AssemblyName"
|
||||
}
|
||||
},
|
||||
"LinuxSettings": {
|
||||
"CreateBinSymlink": "True"
|
||||
},
|
||||
"Win32Settings": {
|
||||
"IncludeUninstaller": "True"
|
||||
},
|
||||
"MacOsSettings": {
|
||||
"CreateBundle": true,
|
||||
"BundleIdentifier": "com.CompanyName.Clario",
|
||||
"SigningCredentialsType": "AdHoc"
|
||||
},
|
||||
"PublishSettings": {
|
||||
"PublishSingleFile": "True",
|
||||
"ExtraBuildProperties": {}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class AccountFromIdConverter : IValueConverter
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not Guid) return null;
|
||||
var accounts = DataRepo.General.Accounts;
|
||||
var accounts = DataRepo.General.FetchAccounts().Result;
|
||||
if (accounts is null) return null;
|
||||
return accounts.FirstOrDefault(x => x.Id == (Guid)value)?.Name;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@ public class AmountSignConverter : IMultiValueConverter
|
||||
{
|
||||
if (values.Any(x => x is null) || values.Count < 2) return 0;
|
||||
if (values[0] is decimal amount && values[1] is string type)
|
||||
return (type.Equals("income", StringComparison.CurrentCultureIgnoreCase) ? amount : -amount);
|
||||
if (parameter is string param && param.Equals("round"))
|
||||
return (type.Equals("income", StringComparison.CurrentCultureIgnoreCase) ? $"${Math.Round(amount)}" : $"-${Math.Round(amount)}");
|
||||
else
|
||||
return (type.Equals("income", StringComparison.CurrentCultureIgnoreCase) ? $"${amount}" : $"-${amount}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
33
Clario/Converters/BoolToColorConverter.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
// BoolToColorConverter.cs
|
||||
public class BoolToColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not bool boolValue || parameter is not string colors)
|
||||
return AvaloniaProperty.UnsetValue;
|
||||
|
||||
var parts = colors.Split('|');
|
||||
if (parts.Length != 2) return AvaloniaProperty.UnsetValue;
|
||||
|
||||
var hex = boolValue ? parts[0] : parts[1];
|
||||
|
||||
if (targetType == typeof(IBrush) || targetType == typeof(SolidColorBrush))
|
||||
return SolidColorBrush.Parse(hex);
|
||||
|
||||
if (targetType == typeof(Color))
|
||||
return Color.Parse(hex);
|
||||
|
||||
return SolidColorBrush.Parse(hex);
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
25
Clario/Converters/BoolToCssConverter.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
// BoolToCssConverter.cs
|
||||
public class BoolToCssConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not bool b || parameter is not string colors)
|
||||
return AvaloniaProperty.UnsetValue;
|
||||
|
||||
var parts = colors.Split('|');
|
||||
if (parts.Length != 2) return AvaloniaProperty.UnsetValue;
|
||||
|
||||
var hex = b ? parts[0] : parts[1];
|
||||
return $"path, circle, rect, ellipse, line, polyline, polygon, text, use {{ stroke: {hex}; }}";
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ public class DecimalSignConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is decimal d) return (d < 0 ? $"-${Math.Abs(Math.Round(d))}" : $"+${Math.Abs(Math.Round(d))}");
|
||||
if (value is decimal d)
|
||||
return (d < 0 ? $"-${Math.Abs(Math.Round(d))}" : $"+${Math.Abs(Math.Round(d))}");
|
||||
return "$0";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ public class PercentageConverter : IMultiValueConverter
|
||||
|
||||
if (value[0] is decimal part && value[1] is decimal total && part > 0)
|
||||
{
|
||||
var percentage = Math.Round(part / total, 2);
|
||||
return percentage.ToString("0%");
|
||||
var percentage = Math.Round(part / total, 3);
|
||||
return percentage.ToString("0.0%");
|
||||
}
|
||||
|
||||
return "0%";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
using Clario.Data;
|
||||
|
||||
namespace Clario.Converters;
|
||||
|
||||
|
||||
@@ -1,50 +1,84 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Clario.CustomControls">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<!-- Add Controls for Previewer Here -->
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="local|DateRangePicker">
|
||||
<Setter Property="MinHeight" Value="15" />
|
||||
<Setter Property="MinWidth" Value="50" />
|
||||
<!-- <Setter Property="Background" Value="{TemplateBinding Background}" /> -->
|
||||
<!-- <Setter Property="Foreground" Value="{TemplateBinding Foreground}" /> -->
|
||||
<!-- <Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" /> -->
|
||||
<!-- <Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" /> -->
|
||||
<!-- <Setter Property="BorderThickness" Value="1" /> -->
|
||||
<Setter Property="Background" Value="{DynamicResource BgBase}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondary}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||
<Setter Property="Padding" Value="10,8" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
|
||||
<!-- Button -->
|
||||
<!-- Trigger button -->
|
||||
<Button x:Name="PART_Button"
|
||||
Content="{TemplateBinding DisplayText}" HorizontalAlignment="Stretch"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontWeight="{TemplateBinding FontWeight}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Svg Grid.Column="0"
|
||||
Path="../Assets/Icons/calendar-days.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{TemplateBinding DisplayText}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
VerticalAlignment="Center" />
|
||||
<Svg Grid.Column="2"
|
||||
Path="../Assets/Icons/chevron-down.svg"
|
||||
Width="12" Height="12"
|
||||
Css="{DynamicResource SvgMuted}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,0,0" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<!-- Popup -->
|
||||
<Popup x:Name="PART_Popup"
|
||||
PlacementTarget="{Binding #PART_Button}"
|
||||
Placement="Bottom"
|
||||
IsLightDismissEnabled="True">
|
||||
|
||||
<Border Padding="8">
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource RadiusControl}"
|
||||
Padding="4"
|
||||
BoxShadow="0 8 32 0 #3C000000">
|
||||
<Calendar x:Name="PART_Calendar"
|
||||
SelectionMode="{TemplateBinding SelectionMode}" />
|
||||
SelectionMode="{TemplateBinding SelectionMode}"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderThickness="0" />
|
||||
</Border>
|
||||
|
||||
</Popup>
|
||||
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- pointerover -->
|
||||
<Style Selector="local|DateRangePicker:pointerover /template/ Button#PART_Button">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderAccent}" />
|
||||
</Style>
|
||||
|
||||
<!-- pressed -->
|
||||
<Style Selector="local|DateRangePicker:pressed /template/ Button#PART_Button">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}" />
|
||||
</Style>
|
||||
|
||||
</Styles>
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Calendar = Avalonia.Controls.Calendar;
|
||||
|
||||
namespace Clario.CustomControls;
|
||||
|
||||
@@ -19,7 +21,6 @@ public class DateRangePicker : TemplatedControl
|
||||
set => SetValue(SelectionModeProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly StyledProperty<IList<DateTime>> SelectedDatesProperty =
|
||||
AvaloniaProperty.Register<DateRangePicker, IList<DateTime>>(
|
||||
nameof(SelectedDates), new List<DateTime>());
|
||||
@@ -30,6 +31,16 @@ public class DateRangePicker : TemplatedControl
|
||||
set => SetValue(SelectedDatesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DateTime?> SelectedDateProperty =
|
||||
AvaloniaProperty.Register<DateRangePicker, DateTime?>(
|
||||
nameof(SelectedDate), null);
|
||||
|
||||
public DateTime? SelectedDate
|
||||
{
|
||||
get => GetValue(SelectedDateProperty);
|
||||
set => SetValue(SelectedDateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> DisplayTextProperty =
|
||||
AvaloniaProperty.Register<DateRangePicker, string>(
|
||||
nameof(DisplayText), "Select Date");
|
||||
@@ -40,114 +51,180 @@ public class DateRangePicker : TemplatedControl
|
||||
set => SetValue(DisplayTextProperty, value);
|
||||
}
|
||||
|
||||
private Button _button;
|
||||
private Popup _popup;
|
||||
private Calendar _calendar;
|
||||
|
||||
private Button? _button;
|
||||
private Popup? _popup;
|
||||
private Calendar? _calendar;
|
||||
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == SelectedDatesProperty && _calendar != null)
|
||||
{
|
||||
_calendar.SelectedDates.Clear();
|
||||
private bool _isSyncing = false;
|
||||
|
||||
foreach (var date in SelectedDates)
|
||||
{
|
||||
_calendar.SelectedDates.Add(date);
|
||||
}
|
||||
|
||||
if (SelectionMode == CalendarSelectionMode.SingleDate)
|
||||
_popup.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
|
||||
if (_button != null) _button.Click -= OnButtonClick;
|
||||
if (_calendar != null) _calendar.SelectedDatesChanged -= OnCalendarDatesChanged;
|
||||
|
||||
_button = e.NameScope.Find<Button>("PART_Button");
|
||||
_popup = e.NameScope.Find<Popup>("PART_Popup");
|
||||
_calendar = e.NameScope.Find<Calendar>("PART_Calendar");
|
||||
|
||||
if (_button != null)
|
||||
{
|
||||
_button.Click += (_, __) => _popup.IsOpen = true;
|
||||
_button.PointerEntered += (_, __) => PseudoClasses.Add(":pointerover");
|
||||
_button.PointerExited += (_, __) => PseudoClasses.Remove(":pointerover");
|
||||
_button.PointerPressed += (_, __) => PseudoClasses.Add(":pressed");
|
||||
_button.PointerReleased += (_, __) => PseudoClasses.Remove(":pressed");
|
||||
}
|
||||
_button.Click += OnButtonClick;
|
||||
|
||||
if (_calendar != null)
|
||||
{
|
||||
_calendar.SelectedDatesChanged += (_, __) =>
|
||||
{
|
||||
SelectedDates.Clear();
|
||||
foreach (var date in _calendar.SelectedDates)
|
||||
{
|
||||
SelectedDates.Add(date);
|
||||
}
|
||||
|
||||
if (SelectionMode == CalendarSelectionMode.SingleDate && SelectedDates.Count == 1) _popup.IsOpen = false;
|
||||
else if (SelectionMode == CalendarSelectionMode.SingleRange && SelectedDates.Count == 2) _popup.IsOpen = false;
|
||||
UpdateDisplayText();
|
||||
};
|
||||
_calendar.AllowTapRangeSelection = true;
|
||||
|
||||
|
||||
_calendar.SelectedDatesChanged += OnCalendarDatesChanged;
|
||||
// _calendar.PointerPressed
|
||||
|
||||
|
||||
SyncToCalendar();
|
||||
}
|
||||
|
||||
UpdateDisplayText();
|
||||
}
|
||||
|
||||
|
||||
private void OnButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
if (_popup is null) return;
|
||||
|
||||
|
||||
SyncToCalendar();
|
||||
_popup.IsOpen = true;
|
||||
}
|
||||
|
||||
private void OnCalendarDatesChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_isSyncing) return;
|
||||
|
||||
if (_popup is null || !_popup.IsOpen) return;
|
||||
Console.WriteLine("test");
|
||||
var newDates = _calendar!.SelectedDates.OrderBy(d => d).ToList();
|
||||
|
||||
_isSyncing = true;
|
||||
try
|
||||
{
|
||||
SelectedDates = newDates;
|
||||
|
||||
|
||||
SelectedDate = newDates.Count > 0 ? newDates[0] : null;
|
||||
|
||||
UpdateDisplayText();
|
||||
|
||||
|
||||
bool shouldClose = SelectionMode switch
|
||||
{
|
||||
CalendarSelectionMode.SingleDate => newDates.Count >= 1,
|
||||
CalendarSelectionMode.SingleRange => newDates.Count >= 2,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (shouldClose)
|
||||
_popup.IsOpen = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (_isSyncing) return;
|
||||
|
||||
if (change.Property == SelectedDatesProperty)
|
||||
{
|
||||
_isSyncing = true;
|
||||
try
|
||||
{
|
||||
var dates = SelectedDates?.OrderBy(d => d).ToList() ?? new List<DateTime>();
|
||||
SelectedDate = dates.Count > 0 ? dates[0] : null;
|
||||
SyncToCalendar();
|
||||
UpdateDisplayText();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
else if (change.Property == SelectedDateProperty)
|
||||
{
|
||||
_isSyncing = true;
|
||||
try
|
||||
{
|
||||
SelectedDates = SelectedDate.HasValue
|
||||
? new List<DateTime> { SelectedDate.Value }
|
||||
: new List<DateTime>();
|
||||
SyncToCalendar();
|
||||
UpdateDisplayText();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SyncToCalendar()
|
||||
{
|
||||
if (_calendar is null || _isSyncing) return;
|
||||
|
||||
_isSyncing = true;
|
||||
try
|
||||
{
|
||||
_calendar.SelectedDates.Clear();
|
||||
if (SelectedDates is not null)
|
||||
{
|
||||
foreach (var date in SelectedDates)
|
||||
_calendar.SelectedDates.Add(date);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisplayText()
|
||||
{
|
||||
if (SelectedDates == null || SelectedDates.Count == 0)
|
||||
var culture = new CultureInfo("en-US");
|
||||
|
||||
if (SelectedDates is null || SelectedDates.Count == 0)
|
||||
{
|
||||
switch (SelectionMode)
|
||||
DisplayText = SelectionMode switch
|
||||
{
|
||||
case CalendarSelectionMode.SingleDate:
|
||||
DisplayText = "Select Date";
|
||||
break;
|
||||
|
||||
case CalendarSelectionMode.SingleRange:
|
||||
DisplayText = "Select Date Range";
|
||||
break;
|
||||
default:
|
||||
DisplayText = "Select Date";
|
||||
break;
|
||||
}
|
||||
|
||||
CalendarSelectionMode.SingleDate => "Select Date",
|
||||
CalendarSelectionMode.SingleRange => "Select Date Range",
|
||||
_ => "Select Date"
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
var ordered = SelectedDates.OrderBy(d => d).ToList();
|
||||
|
||||
switch (SelectionMode)
|
||||
DisplayText = SelectionMode switch
|
||||
{
|
||||
case CalendarSelectionMode.SingleDate:
|
||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
||||
break;
|
||||
CalendarSelectionMode.SingleDate => ordered[0].ToString("MMM dd, yyyy", culture),
|
||||
|
||||
case CalendarSelectionMode.SingleRange:
|
||||
if (ordered.Count == 1)
|
||||
{
|
||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayText =
|
||||
$"{ordered.First():MMM dd, yyyy} → {ordered.Last():MMM dd, yyyy}";
|
||||
}
|
||||
CalendarSelectionMode.SingleRange => ordered.Count == 1
|
||||
? ordered[0].ToString("MMM dd, yyyy", culture)
|
||||
: $"{ordered.First().ToString("MMM dd, yyyy", culture)} → {ordered.Last().ToString("MMM dd, yyyy", culture)}",
|
||||
|
||||
break;
|
||||
CalendarSelectionMode.MultipleRange => string.Join(", ",
|
||||
ordered.Select(d => d.ToString("MMM dd, yyyy", culture))),
|
||||
|
||||
case CalendarSelectionMode.MultipleRange:
|
||||
DisplayText = string.Join(", ",
|
||||
ordered.Select(d => d.ToString("MMM dd, yyyy")));
|
||||
break;
|
||||
|
||||
default:
|
||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
||||
break;
|
||||
}
|
||||
_ => ordered[0].ToString("MMM dd, yyyy", culture)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,15 @@ public class GeneralDataRepo
|
||||
public Profile? Profile { get; set; }
|
||||
public List<Category>? Categories { get; set; }
|
||||
public List<Account>? Accounts { get; set; }
|
||||
public List<Budget>? Budgets { get; set; }
|
||||
public List<Transaction>? Transactions { get; set; }
|
||||
|
||||
public async Task<Profile?> FetchProfileInfo()
|
||||
{
|
||||
if (Profile is not null) return Profile;
|
||||
|
||||
var profile = await SupabaseService.Client.From<Profile>().Get();
|
||||
return profile.Models.FirstOrDefault();
|
||||
Profile = profile.Model;
|
||||
return profile.Model;
|
||||
}
|
||||
|
||||
public async Task InsertProfileInfo(Profile profile)
|
||||
@@ -38,11 +40,42 @@ public class GeneralDataRepo
|
||||
}
|
||||
|
||||
public async Task<List<Transaction>> FetchTransactions()
|
||||
{
|
||||
if (Transactions is not null) return Transactions;
|
||||
var transactions = await SupabaseService.Client.From<Transaction>().Get();
|
||||
Transactions = transactions.Models;
|
||||
return transactions.Models;
|
||||
}
|
||||
|
||||
public async Task InsertTransaction(Transaction transaction)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transactions = await SupabaseService.Client.From<Transaction>().Get();
|
||||
return transactions.Models;
|
||||
await SupabaseService.Client.From<Transaction>().Insert(transaction);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateTransaction(Transaction transaction)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SupabaseService.Client.From<Transaction>().Update(transaction);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteTransaction(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SupabaseService.Client.From<Transaction>().Where(x => x.Id == id).Delete();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -53,19 +86,10 @@ public class GeneralDataRepo
|
||||
|
||||
public async Task<List<Category>> FetchCategories()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Categories is not null) return Categories;
|
||||
var categories = await SupabaseService.Client.From<Category>().Get();
|
||||
Categories = categories.Models;
|
||||
// categories.Models.Select(x=>x.Icon).
|
||||
return categories.Models;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
if (Categories is not null) return Categories;
|
||||
var categories = await SupabaseService.Client.From<Category>().Get();
|
||||
Categories = categories.Models;
|
||||
return categories.Models;
|
||||
}
|
||||
|
||||
public async Task<List<Account>> FetchAccounts()
|
||||
@@ -75,4 +99,78 @@ public class GeneralDataRepo
|
||||
Accounts = accounts.Models;
|
||||
return accounts.Models;
|
||||
}
|
||||
|
||||
public async Task<List<Budget>> FetchBudgets()
|
||||
{
|
||||
if (Budgets is not null) return Budgets;
|
||||
var budgets = await SupabaseService.Client.From<Budget>().Get();
|
||||
Budgets = budgets.Models;
|
||||
return budgets.Models;
|
||||
}
|
||||
|
||||
public async Task<List<Budget>> FetchProcessedBudgets(DateTime CurrentPeriod)
|
||||
{
|
||||
var categories = await FetchCategories();
|
||||
var transactions = await FetchTransactions();
|
||||
var budgets = await FetchBudgets();
|
||||
var outputList = new List<Budget>();
|
||||
foreach (var budget in budgets)
|
||||
{
|
||||
budget.Category = categories.FirstOrDefault(x => x.Id == budget.CategoryId);
|
||||
|
||||
switch (budget.Period.ToLower())
|
||||
{
|
||||
case "monthly":
|
||||
var budgetTransactions = transactions.Where(x =>
|
||||
x.Date.Month == CurrentPeriod.Month && x.Date.Year == CurrentPeriod.Year && x.CategoryId == budget.CategoryId).ToList();
|
||||
budget.Spent = budgetTransactions.Sum(x => x.Amount);
|
||||
budget.TransactionsCount = budgetTransactions.Count;
|
||||
break;
|
||||
case "quarterly":
|
||||
var quarterTransactions = transactions.Where(x =>
|
||||
x.Date.Month >= CurrentPeriod.Month - 3 && x.Date.Month <= CurrentPeriod.Month && x.CategoryId == budget.CategoryId).ToList();
|
||||
budget.Spent = quarterTransactions.Sum(x => x.Amount);
|
||||
budget.TransactionsCount = quarterTransactions.Count;
|
||||
break;
|
||||
case "yearly":
|
||||
var yearTransactions = transactions.Where(x => x.Date.Year == CurrentPeriod.Year && x.CategoryId == budget.CategoryId).ToList();
|
||||
budget.Spent = yearTransactions.Sum(x => x.Amount);
|
||||
budget.TransactionsCount = yearTransactions.Count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (budgets.Any(x => x.IsOnTrack))
|
||||
{
|
||||
outputList.Add(new Budget() { Category = new Category() { Name = "ON TRACK" }, GroupHeader = true });
|
||||
var onTrack = budgets.Where(x => x.IsOnTrack).OrderByDescending(x => x.PercentageUsed).ToList();
|
||||
foreach (var budget in onTrack)
|
||||
{
|
||||
outputList.Add(budget);
|
||||
}
|
||||
}
|
||||
|
||||
if (budgets.Any(x => x.IsWarning))
|
||||
{
|
||||
outputList.Add(new Budget() { Category = new Category() { Name = "APPROACHING LIMIT" }, GroupHeader = true });
|
||||
var approaching = budgets.Where(x => x.IsWarning).OrderByDescending(x => x.PercentageUsed).ToList();
|
||||
foreach (var budget in approaching)
|
||||
{
|
||||
outputList.Add(budget);
|
||||
}
|
||||
}
|
||||
|
||||
if (budgets.Any(x => x.IsOverBudget))
|
||||
{
|
||||
outputList.Add(new Budget() { Category = new Category() { Name = "OVER BUDGET" }, GroupHeader = true });
|
||||
var overBudget = budgets.Where(x => x.IsOverBudget).OrderByDescending(x => x.PercentageUsed).ToList();
|
||||
foreach (var budget in overBudget)
|
||||
{
|
||||
outputList.Add(budget);
|
||||
}
|
||||
}
|
||||
|
||||
return outputList;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using SkiaSharp;
|
||||
|
||||
480
Clario/MobileViews/AccountsViewMobile.axaml
Normal file
@@ -0,0 +1,480 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Clario.MobileViews.AccountsViewMobile"
|
||||
x:DataType="vm:AccountsViewModel"
|
||||
x:Name="AccountsPage"
|
||||
Classes="mobile">
|
||||
<Design.DataContext>
|
||||
<vm:AccountsViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<!-- Root grid — content + overlay stacked -->
|
||||
<Grid>
|
||||
|
||||
<!-- ── Main content ───────────────────────── -->
|
||||
<Grid RowDefinitions="Auto,*"
|
||||
Background="{DynamicResource BgBase}">
|
||||
|
||||
<!-- Top bar -->
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Margin="16,16,16,12">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Accounts"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Classes="accented"
|
||||
Padding="12,8"
|
||||
VerticalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/plus.svg"
|
||||
Width="16" Height="16"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Account list -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="16,0,16,24" Spacing="0">
|
||||
|
||||
<!-- Net worth banner -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="16,14"
|
||||
Margin="0,0,0,16">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource IconBgBlue}"
|
||||
CornerRadius="10"
|
||||
Width="38" Height="38"
|
||||
Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/circle-dollar-sign.svg"
|
||||
Width="17" Height="17"
|
||||
Css="{DynamicResource SvgBlue}" />
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="Total Net Worth"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding TotalBalance, StringFormat='$0.00'}"
|
||||
FontSize="17"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentBlue}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Account cards -->
|
||||
<ItemsControl ItemsSource="{Binding VisibleAccounts}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="10" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Account">
|
||||
<Panel>
|
||||
<!-- Group header -->
|
||||
<TextBlock Text="{Binding Name}"
|
||||
Classes="label"
|
||||
Margin="4,8,0,6"
|
||||
IsVisible="{Binding GroupHeader}" />
|
||||
|
||||
<!-- Account card -->
|
||||
<Button Classes="account"
|
||||
IsVisible="{Binding !GroupHeader}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding DataContext.SelectAccountCommand, ElementName=AccountsPage}"
|
||||
CommandParameter="{Binding .}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="12"
|
||||
Width="44" Height="44"
|
||||
Margin="0,0,14,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Width="20" Height="20"
|
||||
Css="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<!-- Name + institution -->
|
||||
<StackPanel Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="3">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Text="{Binding Institution}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding Mask, Converter={StaticResource MaskToStringConverter}}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Balance + trend -->
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="3">
|
||||
<TextBlock Text="{Binding CurrentBalance, StringFormat='$0.00'}"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" HorizontalAlignment="Right">
|
||||
<Svg Width="11" Height="11">
|
||||
<Svg.Path>
|
||||
<MultiBinding Converter="{StaticResource DecimalColorConverter}">
|
||||
<Binding Path="MonthlyIncrease" />
|
||||
<Binding Source="../Assets/Icons/trending-down.svg" />
|
||||
<Binding Source="../Assets/Icons/trending-up.svg" />
|
||||
</MultiBinding>
|
||||
</Svg.Path>
|
||||
<Svg.Css>
|
||||
<MultiBinding Converter="{StaticResource DecimalColorConverter}">
|
||||
<Binding Path="MonthlyIncrease" />
|
||||
<DynamicResource ResourceKey="SvgRed" />
|
||||
<DynamicResource ResourceKey="SvgGreen" />
|
||||
</MultiBinding>
|
||||
</Svg.Css>
|
||||
</Svg>
|
||||
<TextBlock Text="{Binding MonthlyIncrease, Converter={StaticResource DecimalSignConverter}}"
|
||||
FontSize="11">
|
||||
<TextBlock.Foreground>
|
||||
<MultiBinding Converter="{StaticResource DecimalColorConverter}">
|
||||
<Binding Path="MonthlyIncrease" />
|
||||
<DynamicResource ResourceKey="AccentRed" />
|
||||
<DynamicResource ResourceKey="AccentGreen" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- ── Bottom sheet overlay ───────────────── -->
|
||||
<Grid IsVisible="False"
|
||||
x:Name="OverlayGrid">
|
||||
|
||||
<!-- Dim background -->
|
||||
<Border Background="#80000000"
|
||||
x:Name="DimOverlay" />
|
||||
|
||||
<!-- Sheet -->
|
||||
<Border x:Name="BottomSheet"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
CornerRadius="20,20,0,0"
|
||||
VerticalAlignment="Bottom"
|
||||
MaxHeight="800"
|
||||
Padding="0,0,0,0">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform Y="0" />
|
||||
</Border.RenderTransform>
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
<!-- Sheet handle + header -->
|
||||
<Grid Grid.Row="0" RowDefinitions="Auto,Auto">
|
||||
<!-- Drag handle -->
|
||||
<Border Grid.Row="0"
|
||||
Width="36" Height="4"
|
||||
Background="{DynamicResource BorderAccent}"
|
||||
CornerRadius="2"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,12,0,0" />
|
||||
|
||||
<!-- Header -->
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="Auto,*,Auto"
|
||||
Margin="20,16,20,0">
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="12"
|
||||
Width="44" Height="44"
|
||||
Margin="0,0,14,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding SelectedAccount.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding SelectedAccount.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Width="20" Height="20"
|
||||
Css="{Binding SelectedAccount.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="{Binding SelectedAccount.Name}"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="{Binding SelectedAccount.Institution}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<!-- Close button -->
|
||||
<Button Grid.Column="2"
|
||||
Background="{DynamicResource BgBase}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="20"
|
||||
Width="34" Height="34"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
x:Name="CloseButton">
|
||||
<Svg Path="../Assets/Icons/x.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Sheet scrollable content -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Margin="0,16,0,0">
|
||||
<StackPanel Margin="20,0,20,32" Spacing="14">
|
||||
|
||||
<!-- Balance -->
|
||||
<Border Background="{DynamicResource BgBase}"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="CURRENT BALANCE" Classes="label" />
|
||||
<TextBlock Text="{Binding SelectedAccount.CurrentBalance, StringFormat='$0.00'}"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Details grid -->
|
||||
<Border Background="{DynamicResource BgBase}"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto">
|
||||
<StackPanel Grid.Column="0" Grid.Row="0" Spacing="3" Margin="0,0,0,14">
|
||||
<TextBlock Text="TYPE" Classes="label" />
|
||||
<TextBlock Text="{Binding SelectedAccount.Type}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Grid.Row="0" Spacing="3" Margin="0,0,0,14">
|
||||
<TextBlock Text="INSTITUTION" Classes="label" />
|
||||
<TextBlock Text="{Binding SelectedAccount.Institution}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="0" Grid.Row="1" Spacing="3" Margin="0,0,0,14">
|
||||
<TextBlock Text="ACCOUNT NO." Classes="label" />
|
||||
<TextBlock Text="{Binding SelectedAccount.Mask, Converter={StaticResource MaskToStringConverter}}" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Grid.Row="1" Spacing="3" Margin="0,0,0,14">
|
||||
<TextBlock Text="CURRENCY" Classes="label" />
|
||||
<TextBlock Text="{Binding SelectedAccount.Currency}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="0" Grid.Row="2" Spacing="3">
|
||||
<TextBlock Text="OPENED" Classes="label" />
|
||||
<TextBlock
|
||||
Text="{Binding SelectedAccount.OpenedAt, Converter={StaticResource DateFormatConverter}, ConverterParameter='MMM yyyy'}"
|
||||
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Grid.Row="2" Spacing="3">
|
||||
<TextBlock Text="TRANSACTIONS" Classes="label" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding SelectedAccount.TransactionsCount}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text=" total" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Monthly flow -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="14" Padding="16,14">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="This Month" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="8" Width="32" Height="32"
|
||||
Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/arrow-down-left.svg" Width="14" Height="14" Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="1">
|
||||
<TextBlock Text="Money In" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding SelectedAccount.TotalIncomeThisMonth, StringFormat='$0.00'}" FontSize="14"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource AccentGreen}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="{Binding SelectedAccount.IncomeTransactionsThisMonth}" FontSize="11"
|
||||
Foreground="{DynamicResource TextDisabled}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgRed}" CornerRadius="8" Width="32" Height="32"
|
||||
Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/arrow-up-right.svg" Width="14" Height="14" Css="{DynamicResource SvgRed}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="1">
|
||||
<TextBlock Text="Money Out" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding SelectedAccount.TotalExpenseThisMonth, StringFormat='$0.00'}" FontSize="14"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="{Binding SelectedAccount.ExpenseTransactionsThisMonth}" FontSize="11"
|
||||
Foreground="{DynamicResource TextDisabled}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Net" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource AccentGreen}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource NetworthSumConverter}">
|
||||
<Binding Path="SelectedAccount.TotalIncomeThisMonth" />
|
||||
<Binding Path="SelectedAccount.TotalExpenseThisMonth" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Recent transactions -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="14" Padding="16,14">
|
||||
<StackPanel Spacing="12">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Recent Transactions" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Background="Transparent" BorderThickness="0" Padding="0" Cursor="Hand"
|
||||
Command="{Binding ShowAccountTransactionsCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="View all" FontSize="12" Foreground="{DynamicResource AccentBlue}" VerticalAlignment="Center" />
|
||||
<Svg Path="../Assets/Icons/chevron-right.svg" Width="12" Height="12" Css="{DynamicResource SvgBlue}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<ItemsControl ItemsSource="{Binding SelectedAccount.RecentTransactions}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="12" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Transaction">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" CornerRadius="8" Width="32" Height="32" Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}" Width="14" Height="14"
|
||||
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="1">
|
||||
<TextBlock Text="{Binding Description}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock
|
||||
Text="{Binding Date, Converter={StaticResource DateFormatConverter}, ConverterParameter='MMM d'}"
|
||||
FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource AmountSignConverter}">
|
||||
<Binding Path="Amount" />
|
||||
<Binding Path="Type" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Net worth share -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="14" Padding="16,14">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Net Worth Share" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<ProgressBar Classes="blue" Value="{Binding SelectedAccount.CurrentBalance}" Minimum="0" Maximum="{Binding TotalBalance}"
|
||||
Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Share of total" FontSize="12" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" FontSize="12" FontWeight="SemiBold" Foreground="{DynamicResource AccentBlue}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource PercentageConverter}">
|
||||
<Binding Path="SelectedAccount.CurrentBalance" />
|
||||
<Binding Path="TotalBalance" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Manage -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="14" Padding="16,14">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Manage" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" Margin="0,0,0,2" />
|
||||
<Button Background="Transparent" BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1" CornerRadius="10"
|
||||
Padding="14,10" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Svg Path="../Assets/Icons/archive.svg" Width="14" Height="14" Css="{DynamicResource SvgMuted}" />
|
||||
<StackPanel Spacing="1">
|
||||
<TextBlock Text="Archive Account" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="Hide from active list, keep history" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Background="#2A0D0D" BorderBrush="#3A1515" BorderThickness="1" CornerRadius="10" Padding="14,10"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Svg Path="../Assets/Icons/trash-2.svg" Width="14" Height="14"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
|
||||
<StackPanel Spacing="1">
|
||||
<TextBlock Text="Delete Account" FontSize="12" FontWeight="SemiBold" Foreground="#FF5E5E" />
|
||||
<TextBlock Text="Permanently removes all data" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
118
Clario/MobileViews/AccountsViewMobile.axaml.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Clario.Models;
|
||||
|
||||
namespace Clario.MobileViews;
|
||||
|
||||
public partial class AccountsViewMobile : UserControl
|
||||
{
|
||||
private bool _sheetVisible = false;
|
||||
|
||||
private TranslateTransform SheetTranslate =>
|
||||
(TranslateTransform)BottomSheet.RenderTransform!;
|
||||
|
||||
public AccountsViewMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
DimOverlay.PointerPressed += async (_, _) => await HideSheet();
|
||||
CloseButton.Click += async (_, _) => await HideSheet();
|
||||
|
||||
AddHandler(Button.ClickEvent, async (sender, e) =>
|
||||
{
|
||||
if (e.Source is Button { DataContext: Account }) await ShowSheet();
|
||||
}, handledEventsToo: false);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
BottomSheet.MaxHeight = Bounds.Height * 0.82;
|
||||
|
||||
// update if screen size changes
|
||||
PropertyChanged += (_, args) =>
|
||||
{
|
||||
if (args.Property == BoundsProperty)
|
||||
BottomSheet.MaxHeight = Bounds.Height * 0.82;
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ShowSheet()
|
||||
{
|
||||
if (_sheetVisible) return;
|
||||
_sheetVisible = true;
|
||||
|
||||
OverlayGrid.IsVisible = true;
|
||||
DimOverlay.Opacity = 0;
|
||||
SheetTranslate.Y = 800;
|
||||
|
||||
var sheetAnim = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(320),
|
||||
Easing = new CubicEaseOut(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(TranslateTransform.YProperty, 800d) } },
|
||||
new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(TranslateTransform.YProperty, 0d) } }
|
||||
}
|
||||
};
|
||||
|
||||
var dimAnim = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(220),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(OpacityProperty, 0d) } },
|
||||
new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(OpacityProperty, 1d) } }
|
||||
}
|
||||
};
|
||||
|
||||
await Task.WhenAll(sheetAnim.RunAsync(BottomSheet), dimAnim.RunAsync(DimOverlay));
|
||||
|
||||
SheetTranslate.Y = 0;
|
||||
DimOverlay.Opacity = 1;
|
||||
}
|
||||
|
||||
public async Task HideSheet()
|
||||
{
|
||||
if (!_sheetVisible) return;
|
||||
|
||||
var sheetAnim = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(260),
|
||||
Easing = new CubicEaseIn(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(TranslateTransform.YProperty, 0d) } },
|
||||
new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(TranslateTransform.YProperty, 800d) } }
|
||||
}
|
||||
};
|
||||
|
||||
var dimAnim = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(200),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0d), Setters = { new Setter(OpacityProperty, 1d) } },
|
||||
new KeyFrame { Cue = new Cue(1d), Setters = { new Setter(OpacityProperty, 0d) } }
|
||||
}
|
||||
};
|
||||
|
||||
await Task.WhenAll(sheetAnim.RunAsync(BottomSheet), dimAnim.RunAsync(DimOverlay));
|
||||
|
||||
_sheetVisible = false;
|
||||
OverlayGrid.IsVisible = false;
|
||||
SheetTranslate.Y = 0;
|
||||
DimOverlay.Opacity = 1;
|
||||
}
|
||||
}
|
||||
478
Clario/MobileViews/BudgetViewMobile.axaml
Normal file
@@ -0,0 +1,478 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:views="clr-namespace:Clario.Views"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
|
||||
x:Class="Clario.MobileViews.BudgetViewMobile"
|
||||
x:DataType="vm:BudgetViewModel"
|
||||
Classes="mobile">
|
||||
<Design.DataContext>
|
||||
<vm:BudgetViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid RowDefinitions="Auto,*"
|
||||
Background="{DynamicResource BgBase}">
|
||||
|
||||
<!-- ── Top bar ────────────────────────────── -->
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Margin="16,16,16,12">
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="{Binding CurrentPeriodFormatted}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="Budget"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
Margin="0,2,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Period navigator + add -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center">
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Background="Transparent"
|
||||
Classes="nav"
|
||||
BorderThickness="0"
|
||||
Padding="8,8"
|
||||
Command="{Binding PreviousPeriodCommand}">
|
||||
<Svg Path="../Assets/Icons/chevron-left.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
<Button Background="Transparent"
|
||||
Classes="nav"
|
||||
BorderThickness="0"
|
||||
Padding="8,8"
|
||||
Command="{Binding NextPeriodCommand}">
|
||||
<Svg Path="../Assets/Icons/chevron-right.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button Classes="accented"
|
||||
Padding="10,8">
|
||||
<Svg Path="../Assets/Icons/plus.svg"
|
||||
Width="16" Height="16"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ── Scrollable content ────────────────── -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="16,0,16,24" Spacing="14">
|
||||
|
||||
<!-- ── Period overview strip ─────────── -->
|
||||
<Grid ColumnDefinitions="*,*,*">
|
||||
|
||||
<!-- Budgeted -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="0,0,4,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Budgeted"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding TotalBudgeted, StringFormat='$0'}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Spent -->
|
||||
<Border Grid.Column="1"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="4,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Spent"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding TotalSpent, StringFormat='$0'}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Remaining -->
|
||||
<Border Grid.Column="2"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="4,0,0,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Left"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding TotalLeftFormatted}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentGreen}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ── Overall progress bar ──────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Overall Budget"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding SpentPercentageFormatted}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="green"
|
||||
Value="{Binding TotalSpent}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding TotalBudgeted}"
|
||||
Height="8" />
|
||||
<!-- Status summary -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Background="{DynamicResource IconBgGreen}" CornerRadius="4" Width="8" Height="8" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding OnTrackCountFormatted}" FontSize="11" Foreground="{DynamicResource AccentGreen}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Background="{DynamicResource BadgeBgYellow}" CornerRadius="4" Width="8" Height="8" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding ApproachingCountFormatted}" FontSize="11" Foreground="{DynamicResource AccentYellow}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Background="{DynamicResource BadgeBgRed}" CornerRadius="4" Width="8" Height="8" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding OverBudgetCountFormatted}" FontSize="11" Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Budget cards list ─────────────── -->
|
||||
<ItemsControl ItemsSource="{Binding VisibleBudgets}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="10" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<Panel>
|
||||
<!-- Group header -->
|
||||
<TextBlock IsVisible="{Binding GroupHeader}"
|
||||
Text="{Binding Category.Name}"
|
||||
Classes="label"
|
||||
Margin="4,8,0,2" />
|
||||
|
||||
<!-- Budget card -->
|
||||
<Border IsVisible="{Binding !GroupHeader}"
|
||||
Classes.budget-card="{Binding IsOnTrack}"
|
||||
Classes.budget-card-warning="{Binding IsWarning}"
|
||||
Classes.budget-card-over="{Binding IsOverBudget}"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
CornerRadius="14"
|
||||
Padding="16">
|
||||
<StackPanel Spacing="12">
|
||||
|
||||
<!-- Header -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="10"
|
||||
Width="38" Height="38"
|
||||
Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Width="17" Height="17"
|
||||
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="{Binding Category.Name}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding TransactionsCount}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Spent / limit -->
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2"
|
||||
Margin="0,0,10,0">
|
||||
<TextBlock Text="{Binding SpentFormatted}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<TextBlock Text="{Binding AmountFormatted}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<!-- Menu -->
|
||||
<Button Grid.Column="3"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="4"
|
||||
VerticalAlignment="Center">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg"
|
||||
Width="15" Height="15"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Progress -->
|
||||
<StackPanel Spacing="6">
|
||||
<ProgressBar Classes.green="{Binding IsOnTrack}"
|
||||
Classes.yellow="{Binding IsWarning}"
|
||||
Classes.red="{Binding IsOverBudget}"
|
||||
Value="{Binding Spent}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding LimitAmount}"
|
||||
Height="5" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0"
|
||||
Classes.badge-green="{Binding IsOnTrack}"
|
||||
Classes.badge-warning="{Binding IsWarning}"
|
||||
Classes.badge-over="{Binding IsOverBudget}"
|
||||
CornerRadius="20"
|
||||
Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/triangle-alert.svg"
|
||||
Css="{DynamicResource SvgYellow}"
|
||||
IsVisible="{Binding IsWarning}"
|
||||
Height="11" VerticalAlignment="Center" />
|
||||
<Svg Path="../Assets/Icons/circle-alert.svg"
|
||||
Css="{DynamicResource SvgRed}"
|
||||
IsVisible="{Binding IsOverBudget}"
|
||||
Height="11" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding PercentageFormatted}"
|
||||
FontSize="11"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding RemainingFormatted}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- ── Spending breakdown chart ──────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Spending Breakdown"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<Border Height="130" ClipToBounds="True">
|
||||
<lvc:PieChart Series="{Binding SpendingBreakdownChartSeries}"
|
||||
Height="260"
|
||||
LegendPosition="Hidden"
|
||||
InitialRotation="-180"
|
||||
MaxAngle="180"
|
||||
FlowDirection="LeftToRight"
|
||||
VerticalAlignment="Top"
|
||||
Background="{DynamicResource BgSurface}" />
|
||||
</Border>
|
||||
<ItemsControl ItemsSource="{Binding SpendingBreakdownLegends}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel ItemSpacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4"
|
||||
IsVisible="{Binding !GroupHeader}"
|
||||
Margin="0,0,10,4">
|
||||
<Border Height="10" Width="10" CornerRadius="5"
|
||||
Background="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=brush}" />
|
||||
<TextBlock Text="{Binding Category.Name}" FontSize="11" Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Period progress ───────────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource IconBgBlue}"
|
||||
CornerRadius="8"
|
||||
Width="34" Height="34"
|
||||
Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/calendar-days.svg"
|
||||
Width="16" Height="16"
|
||||
Css="{DynamicResource SvgBlue}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="1">
|
||||
<TextBlock Text="Period Progress" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding PeriodDaysPassed}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" of " FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding PeriodLength}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" days" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding PeriodDaysLeftFormatted}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentBlue}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="blue"
|
||||
Value="{Binding PeriodDaysPassed}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding PeriodLength}"
|
||||
Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Spacing="1">
|
||||
<TextBlock Text="Daily budget left" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="Remaining ÷ days left" FontSize="10" Foreground="{DynamicResource TextDisabled}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding DailyBudgetLeftFormatted}"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Savings goal ──────────────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="12">
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Savings Goal"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="4">
|
||||
<Svg Path="../Assets/Icons/pencil.svg" Width="14" Height="14" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Spacing="6">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Monthly goal" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Profile.SavingsGoal, StringFormat='$0'}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Projected savings" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalLeftFormatted}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentYellow}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Classes="yellow"
|
||||
Value="{Binding TotalLeft}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding Profile.SavingsGoal}"
|
||||
Height="6" />
|
||||
|
||||
<Border Background="{DynamicResource BadgeBgYellow}"
|
||||
CornerRadius="10"
|
||||
Padding="12,8">
|
||||
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="8">
|
||||
<Svg Grid.Column="0"
|
||||
Path="../Assets/Icons/info.svg"
|
||||
Width="14" Height="14"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #F5C842; }"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,1,0,0" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding SavingsHint}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource AccentYellow}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
Clario/MobileViews/BudgetViewMobile.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Clario.MobileViews;
|
||||
|
||||
public partial class BudgetViewMobile : UserControl
|
||||
{
|
||||
public BudgetViewMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
480
Clario/MobileViews/DashboardViewMobile.axaml
Normal file
@@ -0,0 +1,480 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
|
||||
x:DataType="vm:DashboardViewModel"
|
||||
x:Class="Clario.MobileViews.DashboardViewMobile">
|
||||
<Design.DataContext>
|
||||
<vm:DashboardViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid RowDefinitions="Auto,*"
|
||||
Background="{DynamicResource BgBase}">
|
||||
|
||||
<!-- ── Top bar ────────────────────────────── -->
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Margin="16,16,16,12">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Financial Overview"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted"
|
||||
Text="Friday, March 6, 2026"
|
||||
FontSize="12"
|
||||
Margin="0,2,0,0" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Classes="accented"
|
||||
Padding="12,8"
|
||||
VerticalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/plus.svg"
|
||||
Width="16" Height="16"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- ── Scrollable content ────────────────── -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="16,0,16,24" Spacing="14">
|
||||
|
||||
<!-- ── KPI cards ──────────────────────── -->
|
||||
<Grid ColumnDefinitions="*,*">
|
||||
|
||||
<!-- Income -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,12"
|
||||
Margin="0,0,6,0">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Background="{DynamicResource IconBgGreen}"
|
||||
CornerRadius="{StaticResource RadiusIcon}"
|
||||
Padding="5">
|
||||
<Svg Path="../Assets/Icons/trending-up.svg"
|
||||
Height="12" Width="12"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<TextBlock Text="INCOME"
|
||||
Classes="label"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding MonthlyIncome, StringFormat='$0.00'}"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<Border Classes="badge-green" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding MonthlyIncomeChangeFormatted}"
|
||||
FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentGreen}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Expenses -->
|
||||
<Border Grid.Column="1"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,12"
|
||||
Margin="6,0,0,0">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Background="{DynamicResource IconBgOrange}"
|
||||
CornerRadius="{StaticResource RadiusIcon}"
|
||||
Padding="5">
|
||||
<Svg Path="../Assets/Icons/trending-down.svg"
|
||||
Height="12" Width="12"
|
||||
Css="{DynamicResource SvgRed}" />
|
||||
</Border>
|
||||
<TextBlock Text="EXPENSES"
|
||||
Classes="label"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding MonthlyExpenses, StringFormat='$0.00'}"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<Border Classes="badge-red" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding MonthlyExpenseChangeFormatted}"
|
||||
FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Savings rate full width -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,12">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Background="{DynamicResource IconBgPurple}"
|
||||
CornerRadius="{StaticResource RadiusIcon}"
|
||||
Padding="5">
|
||||
<Svg Path="../Assets/Icons/landmark.svg"
|
||||
Height="12" Width="12"
|
||||
Css="{DynamicResource SvgPurple}" />
|
||||
</Border>
|
||||
<TextBlock Text="SAVINGS RATE"
|
||||
Classes="label"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<ProgressBar Grid.Column="0"
|
||||
Classes="green"
|
||||
Minimum="0"
|
||||
Maximum="{Binding MonthlyIncome}"
|
||||
Value="{Binding MonthlyExpenses}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
Margin="12,0,0,0">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource PercentageConverter}">
|
||||
<Binding Path="MonthlyExpenses" />
|
||||
<Binding Path="MonthlyIncome" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Spending by category chart ────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,14">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Spending by Category"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="March 2026" FontSize="11" />
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Column="1"
|
||||
SelectedIndex="0"
|
||||
ItemsSource="{Binding ChartTimePeriods}"
|
||||
SelectedItem="{Binding SelectedChartTimePeriod}"
|
||||
Background="{DynamicResource BgHover}"
|
||||
Foreground="{DynamicResource TextSecondary}"
|
||||
BorderBrush="{DynamicResource BorderAccent}"
|
||||
CornerRadius="{StaticResource RadiusIcon}"
|
||||
Padding="8,5"
|
||||
FontSize="12" />
|
||||
</Grid>
|
||||
|
||||
<lvc:CartesianChart Series="{Binding SpendingByCategoryChartSeries}"
|
||||
Height="180"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
LegendPosition="Hidden"
|
||||
TooltipPosition="Hidden">
|
||||
<lvc:CartesianChart.XAxes>
|
||||
<lvc:XamlAxis IsVisible="False" />
|
||||
</lvc:CartesianChart.XAxes>
|
||||
<lvc:CartesianChart.YAxes>
|
||||
<lvc:XamlLogarithmicAxis LogBase="10" IsVisible="False" MinLimit="1" />
|
||||
</lvc:CartesianChart.YAxes>
|
||||
</lvc:CartesianChart>
|
||||
|
||||
<!-- Category labels -->
|
||||
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:ColumnChartData">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource TextDisabled}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Border HorizontalAlignment="Stretch"
|
||||
Height="1"
|
||||
Background="{DynamicResource BorderSubtle}" />
|
||||
|
||||
<!-- Category amounts -->
|
||||
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:ColumnChartData">
|
||||
<TextBlock Text="{Binding Values, Converter={StaticResource FirstValueConverter}, StringFormat='$0,00'}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{Binding Fill, Converter={StaticResource SkPaintToBrushConverter}}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Recent transactions ───────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,14">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Recent Transactions"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="Last 5 transactions" FontSize="11" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Background="Transparent"
|
||||
Foreground="{DynamicResource AccentBlue}"
|
||||
BorderThickness="0"
|
||||
FontSize="12"
|
||||
Padding="0"
|
||||
Content="View all →"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding ViewAllTransactionsCommand}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Transaction rows -->
|
||||
<Border Background="{DynamicResource BorderSubtle}" CornerRadius="10">
|
||||
<ItemsControl ItemsSource="{Binding RecentTransactions}"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
CornerRadius="10">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Transaction">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
Margin="12,10">
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="9"
|
||||
Width="36" Height="36"
|
||||
Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Height="16" Width="16"
|
||||
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Description}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding Category.Name}" Classes="muted" FontSize="11" />
|
||||
<TextBlock Text="·" Classes="muted" FontSize="11" />
|
||||
<TextBlock
|
||||
Text="{Binding Date, Converter={StaticResource DateFormatConverter}, ConverterParameter='MMM d'}"
|
||||
Classes="muted" FontSize="11" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource AmountSignConverter}">
|
||||
<Binding Path="Amount" />
|
||||
<Binding Path="Type" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Budget tracker ────────────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,14">
|
||||
<StackPanel Spacing="14">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Budget Tracker"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="Monthly limits" FontSize="11" />
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding BudgetsTrackerData}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="14" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6">
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Height="13" Width="13"
|
||||
Css="{DynamicResource SvgSecondary}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding Category.Name}"
|
||||
FontSize="13"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<Panel Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal" IsVisible="{Binding !IsOverBudget}">
|
||||
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" / " FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsOverBudget}">
|
||||
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="11" Foreground="{DynamicResource AccentRed}" />
|
||||
<TextBlock Text=" / " FontSize="11" Foreground="{DynamicResource AccentRed}" />
|
||||
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="11"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
<ProgressBar Classes.green="{Binding IsOnTrack}"
|
||||
Classes.yellow="{Binding IsWarning}"
|
||||
Classes.red="{Binding IsOverBudget}"
|
||||
Minimum="0"
|
||||
Value="{Binding Spent}"
|
||||
Maximum="{Binding LimitAmount}"
|
||||
Height="5" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Accounts summary ──────────────── -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="14,14">
|
||||
<StackPanel Spacing="14">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Accounts"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="{Binding AccountsSubtitle}" FontSize="11" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Background="{DynamicResource BorderSubtle}" CornerRadius="10">
|
||||
<ItemsControl ItemsSource="{Binding AccountsSummaryData}"
|
||||
Background="{DynamicResource BgBase}"
|
||||
CornerRadius="10">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Account">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
Background="{DynamicResource BgBase}"
|
||||
Margin="12,10">
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="9"
|
||||
Width="34" Height="34"
|
||||
Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Height="15" Width="15"
|
||||
Css="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="{Binding Mask, Converter={StaticResource MaskToStringConverter}}"
|
||||
FontSize="11"
|
||||
Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding CurrentBalance, StringFormat='$0'}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Total Balance"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding TotalNetworth, StringFormat=$0}"
|
||||
FontSize="17"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
Clario/MobileViews/DashboardViewMobile.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Clario.MobileViews;
|
||||
|
||||
public partial class DashboardViewMobile : UserControl
|
||||
{
|
||||
public DashboardViewMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
11
Clario/MobileViews/MainAppMobile.axaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="clr-namespace:Clario.Views"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Clario.Views.MainAppMobile"
|
||||
x:CompileBindings="False">
|
||||
<ContentControl Content="{Binding}" />
|
||||
</UserControl>
|
||||
11
Clario/MobileViews/MainAppMobile.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
public partial class MainAppMobile : UserControl
|
||||
{
|
||||
public MainAppMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
114
Clario/MobileViews/MainViewMobile.axaml
Normal file
@@ -0,0 +1,114 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Clario.MobileViews.MainViewMobile"
|
||||
x:CompileBindings="False"
|
||||
Classes="mobile">
|
||||
<Grid RowDefinitions="*,Auto"
|
||||
Background="{DynamicResource BgBase}">
|
||||
|
||||
<!-- ── Content area ──────────────────────── -->
|
||||
<ContentControl Grid.Row="0"
|
||||
Content="{Binding CurrentView}" />
|
||||
|
||||
<!-- ── Bottom tab bar ────────────────────── -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="8,10,8,14">
|
||||
<Grid ColumnDefinitions="*,*,*,*,*">
|
||||
|
||||
<!-- Dashboard -->
|
||||
<Button Grid.Column="0"
|
||||
Classes="nav"
|
||||
Classes.active="{Binding isOnDashboard}"
|
||||
Command="{Binding GoToDashboardCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Padding="0,6">
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/layout-dashboard.svg"
|
||||
Width="22" Height="22"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="Home"
|
||||
FontSize="10"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Transactions -->
|
||||
<Button Grid.Column="1"
|
||||
Classes="nav"
|
||||
Classes.active="{Binding isOnTransactions}"
|
||||
Command="{Binding GoToTransactionsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Padding="0,6">
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/arrow-right-left.svg"
|
||||
Width="22" Height="22"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="Transactions"
|
||||
FontSize="10"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Add (center FAB-style) -->
|
||||
<Button Grid.Column="2"
|
||||
Classes="accented"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Width="52" Height="52"
|
||||
CornerRadius="26"
|
||||
Padding="0">
|
||||
<Svg Path="../Assets/Icons/plus.svg"
|
||||
Width="22" Height="22"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
|
||||
</Button>
|
||||
|
||||
<!-- Accounts -->
|
||||
<Button Grid.Column="3"
|
||||
Classes="nav"
|
||||
Classes.active="{Binding isOnAccounts}"
|
||||
Command="{Binding GoToAccountsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Padding="0,6">
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/chart-pie.svg"
|
||||
Width="22" Height="22"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="Accounts"
|
||||
FontSize="10"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Budget -->
|
||||
<Button Grid.Column="4"
|
||||
Classes="nav"
|
||||
Classes.active="{Binding isOnBudget}"
|
||||
Command="{Binding GoToBudgetCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Padding="0,6">
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
<Svg Path="../Assets/Icons/wallet.svg"
|
||||
Width="22" Height="22"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="Budget"
|
||||
FontSize="10"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
11
Clario/MobileViews/MainViewMobile.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.MobileViews;
|
||||
|
||||
public partial class MainViewMobile : UserControl
|
||||
{
|
||||
public MainViewMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
356
Clario/MobileViews/TransactionsViewMobile.axaml
Normal file
@@ -0,0 +1,356 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Clario.MobileViews.TransactionsViewMobile"
|
||||
x:DataType="vm:TransactionsViewModel"
|
||||
Classes="mobile">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto"
|
||||
Background="{DynamicResource BgBase}">
|
||||
|
||||
<!-- ── Top Bar ───────────────────────────── -->
|
||||
<Grid Grid.Row="0"
|
||||
ColumnDefinitions="*,Auto"
|
||||
Margin="16,16,16,0">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Transactions"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</StackPanel>
|
||||
<!-- Filter button -->
|
||||
<Button Grid.Column="1"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10"
|
||||
Padding="10,8"
|
||||
Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Width="300"
|
||||
BoxShadow="0 8 32 0 #3C000000">
|
||||
<StackPanel Spacing="0">
|
||||
|
||||
<TextBlock Text="FILTERS"
|
||||
Classes="label"
|
||||
Margin="0,0,0,14" />
|
||||
|
||||
<!-- Search -->
|
||||
<TextBox Watermark="Search transactions..."
|
||||
Text="{Binding SearchText}"
|
||||
FontSize="13"
|
||||
Height="38"
|
||||
Padding="12,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="0,0,0,14" />
|
||||
|
||||
<!-- Date range -->
|
||||
<TextBlock Classes="label" Text="DATE RANGE" Margin="0,0,0,6" />
|
||||
<ComboBox HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding DateRangeOptions}"
|
||||
SelectedItem="{Binding SelectedDateRangeOption}"
|
||||
Padding="10,8"
|
||||
FontSize="13"
|
||||
Margin="0,0,0,14" />
|
||||
|
||||
<!-- Type toggle -->
|
||||
<TextBlock Classes="label" Text="TYPE" Margin="0,0,0,6" />
|
||||
<Border Background="{DynamicResource BgBase}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource RadiusControl}"
|
||||
Padding="3"
|
||||
Margin="0,0,0,14">
|
||||
<Grid ColumnDefinitions="*,*,*">
|
||||
<Button Grid.Column="0"
|
||||
Classes="nav"
|
||||
Classes.accented="{Binding FilterTypeAll}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Focusable="False"
|
||||
CornerRadius="7"
|
||||
Padding="0,6"
|
||||
Command="{Binding SetTransactionTypeCommand}"
|
||||
CommandParameter="all">
|
||||
<TextBlock Text="All" FontSize="12" FontWeight="SemiBold" HorizontalAlignment="Center" />
|
||||
</Button>
|
||||
<Button Grid.Column="1"
|
||||
Classes="nav"
|
||||
Classes.accented="{Binding FilterTypeIncome}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Focusable="False"
|
||||
CornerRadius="7"
|
||||
Padding="0,6"
|
||||
Command="{Binding SetTransactionTypeCommand}"
|
||||
CommandParameter="income">
|
||||
<TextBlock Text="Income" FontSize="12" HorizontalAlignment="Center" />
|
||||
</Button>
|
||||
<Button Grid.Column="2"
|
||||
Classes="nav"
|
||||
Classes.accented="{Binding FilterTypeExpense}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Focusable="False"
|
||||
CornerRadius="7"
|
||||
Padding="0,6"
|
||||
Command="{Binding SetTransactionTypeCommand}"
|
||||
CommandParameter="expense">
|
||||
<TextBlock Text="Expense" FontSize="12" HorizontalAlignment="Center" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Category -->
|
||||
<TextBlock Classes="label" Text="CATEGORY" Margin="0,0,0,6" />
|
||||
<ComboBox HorizontalAlignment="Stretch"
|
||||
SelectedItem="{Binding SelectedCategory}"
|
||||
ItemsSource="{Binding Categories}"
|
||||
DisplayMemberBinding="{Binding Name}"
|
||||
Padding="10,8"
|
||||
FontSize="13"
|
||||
Margin="0,0,0,14" />
|
||||
|
||||
<!-- Account -->
|
||||
<TextBlock Classes="label" Text="ACCOUNT" Margin="0,0,0,6" />
|
||||
<ComboBox HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Accounts}"
|
||||
SelectedItem="{Binding SelectedAccount}"
|
||||
DisplayMemberBinding="{Binding Name}"
|
||||
Padding="10,8"
|
||||
FontSize="13"
|
||||
Margin="0,0,0,14" />
|
||||
|
||||
<!-- Sort -->
|
||||
<TextBlock Classes="label" Text="SORT BY" Margin="0,0,0,6" />
|
||||
<ComboBox HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding SortOptions}"
|
||||
SelectedItem="{Binding SelectedSortOption}"
|
||||
Padding="10,8"
|
||||
FontSize="13"
|
||||
Margin="0,0,0,16" />
|
||||
|
||||
<!-- Actions -->
|
||||
<Grid ColumnDefinitions="*,*">
|
||||
<Button Grid.Column="0"
|
||||
Classes="base"
|
||||
Margin="0,0,6,0"
|
||||
Padding="0,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
FontSize="13"
|
||||
Command="{Binding ResetFiltersCommand}"
|
||||
Content="Reset" />
|
||||
<Button Grid.Column="1"
|
||||
Classes="accented"
|
||||
Margin="6,0,0,0"
|
||||
Padding="0,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding ApplyFiltersCommand}">
|
||||
<TextBlock Text="Apply"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource BgBase}"
|
||||
HorizontalAlignment="Center" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/sliders-horizontal.svg"
|
||||
Width="16" Height="16"
|
||||
Css="{DynamicResource SvgSecondary}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- ── Summary strip ─────────────────────── -->
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="*,*,*"
|
||||
Margin="16,12,16,0">
|
||||
|
||||
<!-- Income -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="0,0,4,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Income"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding TotalIncome, StringFormat='$0.00'}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentGreen}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Expenses -->
|
||||
<Border Grid.Column="1"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="4,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Expenses"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding TotalExpenses, StringFormat='$0.00'}"
|
||||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Net -->
|
||||
<Border Grid.Column="2"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="12,10"
|
||||
Margin="4,0,0,0">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="Net"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentBlue}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource NetworthSumConverter}">
|
||||
<Binding Path="TotalIncome" />
|
||||
<Binding Path="TotalExpenses" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ── Transaction list ──────────────────── -->
|
||||
<ScrollViewer Grid.Row="2"
|
||||
Margin="0,12,0,0"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding FilteredTransactions}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:Transaction">
|
||||
<Panel>
|
||||
|
||||
<!-- Date group header -->
|
||||
<Border Padding="16,14,16,6"
|
||||
IsVisible="{Binding GroupHeader}">
|
||||
<TextBlock Text="{Binding Description}"
|
||||
Classes="label" />
|
||||
</Border>
|
||||
|
||||
<!-- Transaction row -->
|
||||
<Border Padding="16,12"
|
||||
IsVisible="{Binding !GroupHeader}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="{DynamicResource RadiusIcon}"
|
||||
Width="38" Height="38"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Width="17" Height="17"
|
||||
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
|
||||
<!-- Description + meta -->
|
||||
<StackPanel Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2">
|
||||
<TextBlock Text="{Binding Description}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Text="{Binding Category.Name}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="·"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextDisabled}" />
|
||||
<TextBlock Text="{Binding Date, Converter={StaticResource DateFormatConverter}}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Amount -->
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding Amount, StringFormat='$0.00'}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<!-- Empty state -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Spacing="12"
|
||||
Margin="0,60"
|
||||
IsVisible="{Binding HasNoTransactions}">
|
||||
<Svg Path="../Assets/Icons/search.svg"
|
||||
Css="{DynamicResource SvgMuted}"
|
||||
Height="36" Width="36"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="No transactions found"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="Try adjusting your filters."
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
11
Clario/MobileViews/TransactionsViewMobile.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.MobileViews;
|
||||
|
||||
public partial class TransactionsViewMobile : UserControl
|
||||
{
|
||||
public TransactionsViewMobile()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
@@ -23,7 +24,7 @@ public class Account : BaseModel
|
||||
[Column("currency")] public string Currency { get; set; } = "USD";
|
||||
|
||||
[Column("opening_balance")] public decimal OpeningBalance { get; set; }
|
||||
public decimal CurrentBalance { get; set; }
|
||||
[JsonIgnore] public decimal CurrentBalance { get; set; }
|
||||
|
||||
[Column("credit_limit")] public decimal? CreditLimit { get; set; }
|
||||
|
||||
@@ -36,13 +37,13 @@ public class Account : BaseModel
|
||||
[Column("icon")] public string Icon { get; set; } = string.Empty;
|
||||
|
||||
[Column("color")] public string Color { get; set; } = string.Empty;
|
||||
|
||||
public int TransactionsCount { get; set; }
|
||||
public int IncomeTransactionsThisMonth { get; set; }
|
||||
public int ExpenseTransactionsThisMonth { get; set; }
|
||||
public decimal TotalIncomeThisMonth { get; set; }
|
||||
public decimal TotalExpenseThisMonth { get; set; }
|
||||
public decimal MonthlyIncrease { get; set; }
|
||||
public List<Transaction>? RecentTransactions { get; set; }
|
||||
public bool GroupHeader { get; set; } = false;
|
||||
|
||||
[JsonIgnore] public int TransactionsCount { get; set; }
|
||||
[JsonIgnore] public int IncomeTransactionsThisMonth { get; set; }
|
||||
[JsonIgnore] public int ExpenseTransactionsThisMonth { get; set; }
|
||||
[JsonIgnore] public decimal TotalIncomeThisMonth { get; set; }
|
||||
[JsonIgnore] public decimal TotalExpenseThisMonth { get; set; }
|
||||
[JsonIgnore] public decimal MonthlyIncrease { get; set; }
|
||||
[JsonIgnore] public List<Transaction>? RecentTransactions { get; set; }
|
||||
[JsonIgnore] public bool GroupHeader { get; set; } = false;
|
||||
}
|
||||
50
Clario/Models/Budget.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
|
||||
namespace Clario.Models;
|
||||
|
||||
[Table("budgets")]
|
||||
public class Budget : BaseModel
|
||||
{
|
||||
[PrimaryKey("id", false)] public Guid Id { get; set; }
|
||||
|
||||
[Column("user_id")] public Guid UserId { get; set; }
|
||||
|
||||
[Column("category_id")] public Guid CategoryId { get; set; }
|
||||
|
||||
[Column("amount")] public decimal LimitAmount { get; set; }
|
||||
|
||||
[Column("period")] public string Period { get; set; } = "monthly";
|
||||
|
||||
[Column("alert_threshold")] public int AlertThreshold { get; set; } = 80;
|
||||
|
||||
[Column("rollover")] public bool Rollover { get; set; }
|
||||
|
||||
[Column("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
// ── not in DB ──────────────────────────────────────
|
||||
|
||||
[JsonIgnore] public Category? Category { get; set; }
|
||||
[JsonIgnore] public int TransactionsCount { get; set; }
|
||||
[JsonIgnore] public decimal Spent { get; set; } // populated after joining with transactions
|
||||
|
||||
[JsonIgnore] public decimal Remaining => LimitAmount - Spent;
|
||||
[JsonIgnore] public double PercentageUsed => LimitAmount > 0 ? Math.Round((double)(Spent / LimitAmount), 2) : 0;
|
||||
[JsonIgnore] public bool IsOverBudget => Spent > LimitAmount;
|
||||
[JsonIgnore] public bool IsWarning => !IsOverBudget && PercentageUsed * 100 >= AlertThreshold;
|
||||
[JsonIgnore] public bool IsOnTrack => !IsOverBudget && PercentageUsed * 100 < AlertThreshold;
|
||||
|
||||
[JsonIgnore] public string SpentFormatted => $"${Spent:N0}";
|
||||
[JsonIgnore] public string AmountFormatted => $"of ${LimitAmount:N0}";
|
||||
[JsonIgnore] public string PercentageFormatted => $"{PercentageUsed:P0} used";
|
||||
|
||||
[JsonIgnore]
|
||||
public string RemainingFormatted => IsOverBudget
|
||||
? $"${Math.Abs(Remaining):N0} over"
|
||||
: $"${Remaining:N0} left";
|
||||
|
||||
[JsonIgnore] public bool GroupHeader { get; set; } = false;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
|
||||
18
Clario/Models/ColumnChartData.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Clario.Models;
|
||||
|
||||
public partial class ColumnChartData : ObservableObject
|
||||
{
|
||||
public Guid id;
|
||||
[ObservableProperty] private string _name;
|
||||
[ObservableProperty] private double[] _values;
|
||||
[ObservableProperty] private SolidColorPaint _fill;
|
||||
|
||||
|
||||
[JsonIgnore] public Func<ChartPoint, string> ToolTipFormatter => point => $"${point.Coordinate.PrimaryValue:N0}";
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using ExCSS;
|
||||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
@@ -14,6 +13,7 @@ public class Profile : BaseModel
|
||||
[Column("currency")] public string Currency { get; set; }
|
||||
[Column("theme")] public string Theme { get; set; }
|
||||
[Column("language")] public string Language { get; set; }
|
||||
[Column("savings_goal")] public decimal? SavingsGoal { get; set; }
|
||||
[Column("created_at")] public DateTime CreatedAt { get; set; }
|
||||
[Column("updated_at")] public DateTime UpdatedAt { get; set; }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Clario.Data;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using Supabase.Postgrest.Attributes;
|
||||
using Supabase.Postgrest.Models;
|
||||
|
||||
@@ -26,11 +26,12 @@ public class Transaction : BaseModel
|
||||
{
|
||||
_categoryId = value;
|
||||
|
||||
Category = DataRepo.General.Categories?.FirstOrDefault(x => x.Id == value);
|
||||
Category = DataRepo.General.FetchCategories().Result.FirstOrDefault(x => x.Id == value);
|
||||
}
|
||||
}
|
||||
|
||||
public Category? Category { get; set; }
|
||||
[JsonIgnore] public Category? Category { get; set; }
|
||||
|
||||
[Column("amount")] public decimal Amount { get; set; }
|
||||
|
||||
[Column("type")] public string Type { get; set; } = string.Empty; // "income" or "expense"
|
||||
@@ -40,8 +41,8 @@ public class Transaction : BaseModel
|
||||
[Column("note")] public string? Note { get; set; }
|
||||
|
||||
[Column("date")] public DateTime Date { get; set; }
|
||||
|
||||
|
||||
[Column("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
public bool GroupHeader { get; set; } = false;
|
||||
[JsonIgnore] public bool GroupHeader { get; set; } = false;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using Avalonia;
|
||||
using Avalonia;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Clario.Services;
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
<StyleInclude Source="Styles/ToggleSwitchStyles.axaml" />
|
||||
<StyleInclude Source="Styles/CalenderItemStyles.axaml" />
|
||||
<!-- <StyleInclude Source="Styles/CalenderItemStyles.axaml" /> -->
|
||||
<StyleInclude Source="Styles/CalendarStyles.axaml" />
|
||||
<StyleInclude Source="../CustomControls/DateRangePicker.axaml" />
|
||||
<Styles.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -87,7 +88,6 @@
|
||||
|
||||
<!-- ACCENTS -->
|
||||
<SolidColorBrush x:Key="AccentBlue" Color="#7B9CFF" />
|
||||
|
||||
<SolidColorBrush x:Key="AccentGreen" Color="#2ECC8A" />
|
||||
<SolidColorBrush x:Key="AccentYellow" Color="#F5C842" />
|
||||
<SolidColorBrush x:Key="AccentRed" Color="#FF5E5E" />
|
||||
@@ -463,8 +463,8 @@
|
||||
|
||||
<Style Selector="Button.nav">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextDisabled}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgMuted}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="{DynamicResource NavButtonPadding}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||
@@ -487,8 +487,40 @@
|
||||
|
||||
<Style Selector="Button.nav:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextDisabled}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgMuted}" />
|
||||
<Setter Property="Opacity" Value="0.4" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector=".mobile Button.nav">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgMuted}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="{DynamicResource NavButtonPadding}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="FocusAdorner" Value="{x:Null}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".mobile Button.nav:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextSecondary}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgSecondary}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".mobile Button.nav:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextPrimary}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgPrimary}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".mobile Button.nav:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgMuted}" />
|
||||
<Setter Property="Opacity" Value="0.4" />
|
||||
</Style>
|
||||
|
||||
@@ -513,12 +545,24 @@
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgBlue}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".mobile Button.nav:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextDisabled}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".mobile Button.nav:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextDisabled}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
||||
</Style>
|
||||
|
||||
<!-- ACCENTED BUTTON -->
|
||||
|
||||
<Style Selector="Button.accented">
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource BgBase}" />
|
||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgBase}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource FontSizeBody}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||
@@ -559,11 +603,15 @@
|
||||
|
||||
<Style Selector="Button.base:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextPrimary}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.base:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.base:disabled /template/ ContentPresenter">
|
||||
@@ -578,6 +626,19 @@
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="Border.editable Button.reveal">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusSmall}" />
|
||||
</Style>
|
||||
<Style Selector="Border.editable:pointerover Button.reveal">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
<Style Selector="Border.editable Svg.hide">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
<Style Selector="Border.editable:pointerover Svg.hide">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<!-- TOGGLE BUTTON -->
|
||||
|
||||
<Style Selector="ToggleButton">
|
||||
@@ -685,6 +746,25 @@
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</Style>
|
||||
|
||||
<!-- DATE RANGE PICKER -->
|
||||
|
||||
<Style Selector="cc|DateRangePicker.ghost">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="cc|DateRangePicker.ghost:pointerover /template/ Button#PART_Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="cc|DateRangePicker.ghost:pressed /template/ Button#PART_Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- COMBOBOX -->
|
||||
|
||||
<Style Selector="ComboBox">
|
||||
|
||||
278
Clario/Theme/Styles/CalendarStyles.axaml
Normal file
@@ -0,0 +1,278 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Background="#0D0F14">
|
||||
<Calendar SelectionMode="SingleRange"/>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- ── FluentCalendarButton (header + prev/next) ──────── -->
|
||||
<Style Selector="Button.FluentCalendarButton, Button#PART_HeaderButton, Button#PART_PreviousButton, Button#PART_NextButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FocusAdorner" Value="{x:Null}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.FluentCalendarButton:pointerover, Button#PART_HeaderButton:pointerover, Button#PART_PreviousButton:pointerover, Button#PART_NextButton:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.FluentCalendarButton:pressed, Button#PART_HeaderButton:pressed, Button#PART_PreviousButton:pressed, Button#PART_NextButton:pressed">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}"/>
|
||||
</Style>
|
||||
|
||||
<!-- ── Calendar root ──────────────────────────────────── -->
|
||||
<Style Selector="Calendar">
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BgSurface}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource BgSurface}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}"/>
|
||||
</Style>
|
||||
|
||||
<!-- ── CalendarItem (the main container) ─────────────── -->
|
||||
<Style Selector="CalendarItem">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource BgSurface}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}"/>
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
ClipToBounds="True">
|
||||
<Grid VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
RowDefinitions="44,*"
|
||||
MinWidth="294"
|
||||
Background="{TemplateBinding Background}">
|
||||
|
||||
<!-- Header row: title + prev/next -->
|
||||
<Grid ColumnDefinitions="*,36,36" Margin="8,0,4,0">
|
||||
<Button Name="PART_HeaderButton"
|
||||
Theme="{StaticResource FluentCalendarButton}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Padding="8,0,0,0"
|
||||
CornerRadius="{DynamicResource RadiusControl}"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"/>
|
||||
<Button Name="PART_PreviousButton"
|
||||
Grid.Column="1"
|
||||
Theme="{StaticResource FluentCalendarButton}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
CornerRadius="{DynamicResource RadiusControl}"
|
||||
Width="32" Height="32"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Path Stroke="{DynamicResource TextMuted}"
|
||||
StrokeThickness="1.5"
|
||||
Data="M 0,8 L 8,0 L 16,8"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Button>
|
||||
<Button Name="PART_NextButton"
|
||||
Grid.Column="2"
|
||||
Theme="{StaticResource FluentCalendarButton}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
CornerRadius="{DynamicResource RadiusControl}"
|
||||
Width="32" Height="32"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Path Stroke="{DynamicResource TextMuted}"
|
||||
StrokeThickness="1.5"
|
||||
Data="M 0,0 L 8,8 L 16,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Separator under header -->
|
||||
<Border Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
Height="1"
|
||||
Background="{DynamicResource BorderSubtle}"
|
||||
Margin="8,0"/>
|
||||
|
||||
<!-- Month grid -->
|
||||
<Grid Name="PART_MonthView"
|
||||
Grid.Row="1"
|
||||
IsVisible="False"
|
||||
MinHeight="290"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
|
||||
<!-- Year/decade grid -->
|
||||
<Grid Name="PART_YearView"
|
||||
Grid.Row="1"
|
||||
MinHeight="290"
|
||||
IsVisible="False"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ── CalendarDayButton ──────────────────────────────── -->
|
||||
<Style Selector="CalendarDayButton">
|
||||
<Setter Property="Width" Value="38"/>
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondary}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FocusAdorner" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Panel>
|
||||
<Border Name="Root"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{DynamicResource RadiusIcon}"
|
||||
ClipToBounds="True">
|
||||
<ContentPresenter Name="PART_ContentPresenter"
|
||||
Content="{TemplateBinding Content}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<Border Name="Border"
|
||||
BorderThickness="2"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
CornerRadius="{DynamicResource RadiusIcon}"/>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- hover -->
|
||||
<Style Selector="CalendarDayButton:pointerover /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}"/>
|
||||
</Style>
|
||||
|
||||
<!-- pressed -->
|
||||
<Style Selector="CalendarDayButton:pressed /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}"/>
|
||||
</Style>
|
||||
|
||||
<!-- selected -->
|
||||
<Style Selector="CalendarDayButton:selected /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource BgBase}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:selected:pointerover /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- today -->
|
||||
<Style Selector="CalendarDayButton:today /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource IconBgBlue}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:today /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentBlue}"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:today:selected /template/ Border#Root">
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:today:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource BgBase}"/>
|
||||
</Style>
|
||||
|
||||
<!-- inactive (days from prev/next month) -->
|
||||
<Style Selector="CalendarDayButton:inactive /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextDisabled}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarDayButton:inactive /template/ Border#Root">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<!-- blackout -->
|
||||
<Style Selector="CalendarDayButton:blackout /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextDisabled}"/>
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Style>
|
||||
|
||||
<!-- ── CalendarButton (month/year picker cells) ──────── -->
|
||||
<Style Selector="CalendarButton">
|
||||
<Setter Property="Width" Value="60"/>
|
||||
<Setter Property="Height" Value="52"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondary}"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FocusAdorner" Value="{x:Null}"/>
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarButton:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BgHover}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextPrimary}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarButton:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarButton:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource BgBase}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarButton:today /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource IconBgBlue}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentBlue}"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
|
||||
</Styles>
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<Style Selector="Calendar">
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BgSurface}"></Setter>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BgSurface}"/>
|
||||
</Style>
|
||||
<Style Selector="CalendarItem">
|
||||
<Setter Property="Foreground" Value="{DynamicResource CalendarViewForeground}" />
|
||||
|
||||
@@ -22,6 +22,14 @@ public class ViewLocator : IDataTemplate
|
||||
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (App.IsMobile)
|
||||
{
|
||||
var mobileName = name.Replace(".Views.", ".MobileViews.") + "Mobile";
|
||||
var mobileType = Type.GetType(mobileName);
|
||||
if (mobileType != null)
|
||||
return (Control)Activator.CreateInstance(mobileType)!;
|
||||
}
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Clario.Data;
|
||||
using Clario.Models;
|
||||
using Clario.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
@@ -15,30 +13,29 @@ namespace Clario.ViewModels;
|
||||
public partial class AccountsViewModel : ViewModelBase
|
||||
{
|
||||
public required ViewModelBase parentViewModel;
|
||||
private List<Account> _accounts = new();
|
||||
public required List<Account> Accounts = new();
|
||||
public required List<Transaction> Transactions = new();
|
||||
[ObservableProperty] private ObservableCollection<Account> _visibleAccounts = new();
|
||||
[ObservableProperty] private decimal _totalBalance = 0;
|
||||
[ObservableProperty] private Account _selectedAccount;
|
||||
|
||||
public AccountsViewModel()
|
||||
{
|
||||
_ = Initialize();
|
||||
|
||||
}
|
||||
|
||||
private async Task Initialize()
|
||||
public async Task Initialize()
|
||||
{
|
||||
_accounts = await DataRepo.General.FetchAccounts();
|
||||
await FetchAndProcessAccountInfo();
|
||||
FetchAndProcessAccountInfo();
|
||||
GroupAccounts();
|
||||
SelectedAccount = VisibleAccounts.First(x => !x.GroupHeader);
|
||||
}
|
||||
|
||||
private async Task FetchAndProcessAccountInfo()
|
||||
private void FetchAndProcessAccountInfo()
|
||||
{
|
||||
var transactions = await DataRepo.General.FetchTransactions();
|
||||
foreach (var account in _accounts)
|
||||
foreach (var account in Accounts)
|
||||
{
|
||||
var accountTransactions = transactions.Where(t => t.AccountId == account.Id).ToList();
|
||||
var accountTransactions = Transactions.Where(t => t.AccountId == account.Id).ToList();
|
||||
account.TransactionsCount = accountTransactions.Count;
|
||||
account.CurrentBalance = account.OpeningBalance + accountTransactions.Sum(t => t.Type == "income" ? t.Amount : -t.Amount);
|
||||
account.TotalIncomeThisMonth = accountTransactions.Where(t => t.Date.Month == DateTime.Now.Month && t.Type == "income").Sum(t => t.Amount);
|
||||
@@ -65,7 +62,7 @@ public partial class AccountsViewModel : ViewModelBase
|
||||
|
||||
foreach (var type in accountTypes)
|
||||
{
|
||||
var accountsOfType = _accounts.Where(a => a.Type == type.Key).ToList();
|
||||
var accountsOfType = Accounts.Where(a => a.Type == type.Key).ToList();
|
||||
if (accountsOfType.Any())
|
||||
{
|
||||
var header = new Account { Name = type.Value.ToUpper(), GroupHeader = true };
|
||||
|
||||
@@ -3,10 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Clario.Data;
|
||||
using Clario.Models.GeneralModels;
|
||||
using Clario.Services;
|
||||
using Clario.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Supabase.Gotrue;
|
||||
@@ -37,6 +34,7 @@ public partial class AuthViewModel : ViewModelBase
|
||||
|
||||
public AuthViewModel()
|
||||
{
|
||||
Console.WriteLine("auth vm loaded");
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Clario.ViewModels;
|
||||
namespace Clario.ViewModels;
|
||||
|
||||
public partial class BudgetCardMenuViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Clario.ViewModels;
|
||||
namespace Clario.ViewModels;
|
||||
|
||||
public partial class BudgetFormViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Media;
|
||||
using System.Threading.Tasks;
|
||||
using Clario.Data;
|
||||
using Clario.Models;
|
||||
using Clario.Models.GeneralModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -14,39 +20,115 @@ namespace Clario.ViewModels;
|
||||
public partial class BudgetViewModel : ViewModelBase
|
||||
{
|
||||
public required ViewModelBase parentViewModel;
|
||||
[ObservableProperty] private Profile? _profile;
|
||||
public required List<Budget> Budgets = new();
|
||||
[ObservableProperty] private ObservableCollection<Budget> _visibleBudgets = new();
|
||||
public required List<Category> Categories = new();
|
||||
public required List<Transaction> Transactions = new();
|
||||
|
||||
[ObservableProperty] private ObservableCollection<PieData> _spendingBreakdown =
|
||||
[
|
||||
new() { Name = "Food & Dining", Values = [340d], Fill = new SolidColorPaint(SKColor.Parse("#2ECC8A")), InnerRadius = 60 },
|
||||
new() { Name = "Housing", Values = [540d], Fill = new SolidColorPaint(SKColor.Parse("#FF7E5E")), InnerRadius = 60 },
|
||||
new() { Name = "Transport", Values = [110d], Fill = new SolidColorPaint(SKColor.Parse("#7B9CFF")), InnerRadius = 60 },
|
||||
new() { Name = "Shopping", Values = [380d], Fill = new SolidColorPaint(SKColor.Parse("#FF5E5E")), InnerRadius = 60 },
|
||||
new() { Name = "Entertainment", Values = [170d], Fill = new SolidColorPaint(SKColor.Parse("#9B7BFF")), InnerRadius = 60 },
|
||||
new() { Name = "Health", Values = [69d], Fill = new SolidColorPaint(SKColor.Parse("#FF5E9B")), InnerRadius = 60 }
|
||||
];
|
||||
}
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(NextPeriodCommand), nameof(PreviousPeriodCommand))]
|
||||
private DateTime _currentPeriod = DateTime.Now.Date;
|
||||
|
||||
public partial class PieData : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _name;
|
||||
[ObservableProperty] private double[] _values;
|
||||
[ObservableProperty] private SolidColorPaint _fill;
|
||||
[ObservableProperty] private IBrush _bg;
|
||||
[ObservableProperty] private double _innerRadius = 60;
|
||||
[ObservableProperty] private Func<ChartPoint, string> _formatter;
|
||||
public bool CanGoToNextPeriod => CurrentPeriod.Month < DateTime.Now.Month;
|
||||
public bool CanGoToPreviousPeriod => CurrentPeriod.Month > Transactions.Min(x => x.Date.Month);
|
||||
public string CurrentPeriodFormatted => CurrentPeriod.ToString("MMMM yyyy");
|
||||
|
||||
partial void OnFillChanged(SolidColorPaint value)
|
||||
[ObservableProperty] private ISeries[] _spendingBreakdownChartSeries = [];
|
||||
[ObservableProperty] private List<Budget> _spendingBreakdownLegends = [];
|
||||
|
||||
[ObservableProperty] private decimal _totalSpent;
|
||||
[ObservableProperty] private decimal _totalBudgeted;
|
||||
public string SpentPercentageFormatted => (TotalSpent / TotalBudgeted).ToString("P0") + " of total budget.";
|
||||
|
||||
public decimal TotalLeft => Math.Clamp(Math.Round(TotalBudgeted - TotalSpent), 0, decimal.MaxValue);
|
||||
public string TotalLeftFormatted => TotalLeft.ToString("C0") + " left";
|
||||
|
||||
public string SavingsHint => TotalLeft >= (Profile != null ? Profile.SavingsGoal : 0)
|
||||
? "You're on track!"
|
||||
: $"Reduce your spending by ${Math.Round((Profile != null ? Profile.SavingsGoal ?? 0 : 0) - TotalLeft)} to hit your goal.";
|
||||
|
||||
private int _onTrackCount;
|
||||
private int _approachingCount;
|
||||
private int _overBudgetCount;
|
||||
|
||||
public string OnTrackCountFormatted => _onTrackCount == 1 ? _onTrackCount + " Budget" : _onTrackCount + " Budgets";
|
||||
public string ApproachingCountFormatted => _approachingCount == 1 ? _approachingCount + " Budget" : _approachingCount + " Budgets";
|
||||
public string OverBudgetCountFormatted => _overBudgetCount == 1 ? _overBudgetCount + " Budget" : _overBudgetCount + " Budgets";
|
||||
|
||||
public int PeriodLength => DateTime.DaysInMonth(CurrentPeriod.Year, CurrentPeriod.Month);
|
||||
public int PeriodDaysPassed => DateTime.Now.Day;
|
||||
private int PeriodDaysLeft => PeriodLength - PeriodDaysPassed;
|
||||
public string PeriodDaysLeftFormatted => PeriodDaysLeft == 1 ? PeriodDaysLeft + " day left" : PeriodDaysLeft + " days left";
|
||||
|
||||
public string DailyBudgetLeftFormatted => ((TotalBudgeted - TotalSpent) / PeriodDaysLeft).ToString("C", new CultureInfo("en-US"));
|
||||
|
||||
public BudgetViewModel()
|
||||
{
|
||||
var color = Color.FromArgb(value.Color.Alpha, value.Color.Red, value.Color.Green, value.Color.Blue);
|
||||
Bg = new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
public PieData()
|
||||
public async Task Initialize()
|
||||
{
|
||||
Formatter = point =>
|
||||
await ProcessBudgets();
|
||||
ProcessChartData();
|
||||
}
|
||||
|
||||
private void ProcessChartData()
|
||||
{
|
||||
var categories = Categories;
|
||||
var transactions = Transactions;
|
||||
var tempCategorySpendingBreakdown = new List<(Category category, double[] spent)>();
|
||||
var tempSpendingBreakdownLegends = new List<Budget>();
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var pct = point.StackedValue!.Share * 100;
|
||||
return $"${point.Coordinate.PrimaryValue:N0} ({pct:N1}%)";
|
||||
};
|
||||
var spent = transactions
|
||||
.Where(x => x.CategoryId == category.Id && x.Type.Equals("expense", StringComparison.OrdinalIgnoreCase) &&
|
||||
x.Date.Month == CurrentPeriod.Month && x.Date.Year == CurrentPeriod.Year)
|
||||
.Sum(x => x.Amount);
|
||||
if (spent == 0) continue;
|
||||
double[] values = [(double)spent];
|
||||
tempCategorySpendingBreakdown.Add((category, values));
|
||||
tempSpendingBreakdownLegends.Add(new Budget() { Category = category, Spent = spent });
|
||||
}
|
||||
|
||||
|
||||
SpendingBreakdownChartSeries = tempCategorySpendingBreakdown.OrderByDescending(x => x.spent.Sum()).Select(x => (ISeries)new XamlPieSeries()
|
||||
{
|
||||
Name = x.category.Name,
|
||||
Values = x.spent,
|
||||
Fill = new SolidColorPaint(SKColor.Parse(x.category.Color)),
|
||||
InnerRadius = 60,
|
||||
ToolTipLabelFormatter = point => $"${point.Coordinate.PrimaryValue:N0}"
|
||||
}).ToArray();
|
||||
|
||||
SpendingBreakdownLegends = tempSpendingBreakdownLegends.OrderByDescending(x => x.Spent).ToList();
|
||||
}
|
||||
|
||||
private async Task ProcessBudgets()
|
||||
{
|
||||
VisibleBudgets.Clear();
|
||||
VisibleBudgets = new ObservableCollection<Budget>(await DataRepo.General.FetchProcessedBudgets(CurrentPeriod));
|
||||
_onTrackCount = VisibleBudgets.Count(x => x.IsOnTrack);
|
||||
_approachingCount = VisibleBudgets.Count(x => x.IsWarning);
|
||||
_overBudgetCount = VisibleBudgets.Count(x => x.IsOverBudget);
|
||||
TotalBudgeted = VisibleBudgets.Sum(x => x.LimitAmount);
|
||||
TotalSpent = VisibleBudgets.Sum(x => x.Spent);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanGoToNextPeriod))]
|
||||
private async Task NextPeriod()
|
||||
{
|
||||
CurrentPeriod = CurrentPeriod.AddMonths(1);
|
||||
OnPropertyChanged(nameof(CurrentPeriodFormatted));
|
||||
ProcessChartData();
|
||||
await ProcessBudgets();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanGoToPreviousPeriod))]
|
||||
private async Task PreviousPeriod()
|
||||
{
|
||||
CurrentPeriod = CurrentPeriod.AddMonths(-1);
|
||||
OnPropertyChanged(nameof(CurrentPeriodFormatted));
|
||||
ProcessChartData();
|
||||
await ProcessBudgets();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Security.Authentication;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Skia;
|
||||
using Avalonia.Styling;
|
||||
using Clario.Data;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Clario.Extensions;
|
||||
using Clario.Services;
|
||||
using LiveChartsCore.Kernel;
|
||||
using Clario.Models;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using ShimSkiaSharp;
|
||||
using SKColor = SkiaSharp.SKColor;
|
||||
|
||||
namespace Clario.ViewModels;
|
||||
@@ -20,13 +18,98 @@ namespace Clario.ViewModels;
|
||||
public partial class DashboardViewModel : ViewModelBase
|
||||
{
|
||||
public required ViewModelBase parentViewModel;
|
||||
[ObservableProperty] private ObservableCollection<ChartData> _chartData = new();
|
||||
public required List<Transaction> Transactions = new();
|
||||
public required List<Category> Categories = new();
|
||||
public required List<Budget> Budgets = new();
|
||||
public required List<Account> Accounts = new();
|
||||
|
||||
[ObservableProperty] private ObservableCollection<ColumnChartData> _spendingByCategoryChartData = new();
|
||||
[ObservableProperty] private ISeries[] _spendingByCategoryChartSeries = new ISeries[] { };
|
||||
[ObservableProperty] private ObservableCollection<Budget> _budgetsTrackerData = new();
|
||||
[ObservableProperty] private ObservableCollection<Account> _accountsSummaryData = new();
|
||||
[ObservableProperty] private ObservableCollection<Transaction> _recentTransactions = new();
|
||||
[ObservableProperty] private decimal _totalNetworth;
|
||||
[ObservableProperty] private decimal _monthlyIncome;
|
||||
private decimal _monthlyIncomeChange;
|
||||
|
||||
public string MonthlyIncomeChangeFormatted => _monthlyIncomeChange >= 0
|
||||
? "↑" + _monthlyIncomeChange.ToString("0.0%")
|
||||
: "↓" + _monthlyIncomeChange.ToString("0.0%");
|
||||
|
||||
[ObservableProperty] private decimal _monthlyExpenses;
|
||||
private decimal _monthlyExpensesChange;
|
||||
|
||||
public string MonthlyExpenseChangeFormatted => _monthlyExpensesChange >= 0
|
||||
? "↑" + _monthlyExpensesChange.ToString("0.0%")
|
||||
: "↓" + _monthlyExpensesChange.ToString("0.0%");
|
||||
|
||||
public string AccountsSubtitle =>
|
||||
AccountsSummaryData.Count == 1 ? $" {AccountsSummaryData.Count} linked Account" : $"{AccountsSummaryData.Count} linked Accounts";
|
||||
|
||||
[ObservableProperty] private List<string> _chartTimePeriods = new()
|
||||
{
|
||||
"This Month",
|
||||
"Last Month",
|
||||
"This Quarter",
|
||||
"This Year"
|
||||
};
|
||||
|
||||
[ObservableProperty] private string _selectedChartTimePeriod = "This Month";
|
||||
|
||||
partial void OnSelectedChartTimePeriodChanged(string value)
|
||||
{
|
||||
ChartTimePeriod period = value switch
|
||||
{
|
||||
"This Month" => ChartTimePeriod.ThisMonth,
|
||||
"Last Month" => ChartTimePeriod.LastMonth,
|
||||
"This Quarter" => ChartTimePeriod.ThisQuarter,
|
||||
"This Year" => ChartTimePeriod.ThisYear,
|
||||
_ => ChartTimePeriod.ThisMonth
|
||||
};
|
||||
|
||||
UpdateSpendingByCategoryChart(period);
|
||||
}
|
||||
|
||||
public DashboardViewModel()
|
||||
{
|
||||
var app = Application.Current;
|
||||
if (app is null) return;
|
||||
|
||||
}
|
||||
|
||||
public void initialize()
|
||||
{
|
||||
UpdateUserOverview();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void UpdateUserOverview()
|
||||
{
|
||||
var thisMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
|
||||
var lastMonth = thisMonth.AddMonths(-1);
|
||||
|
||||
MonthlyIncome = Transactions.Where(x => x.Type == "income" && x.Date.Month == thisMonth.Month && x.Date.Year == thisMonth.Year)
|
||||
.Sum(x => x.Amount);
|
||||
MonthlyExpenses = Transactions.Where(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month && x.Date.Year == DateTime.Now.Year)
|
||||
.Sum(x => x.Amount);
|
||||
var lastMonthIncome = Transactions.Where(x => x.Type == "income" && x.Date.Month == lastMonth.Month && x.Date.Year == lastMonth.Year)
|
||||
.Sum(x => x.Amount);
|
||||
var lastMonthExpenses = Transactions.Where(x => x.Type == "expense" && x.Date.Month == lastMonth.Month && x.Date.Year == lastMonth.Year)
|
||||
.Sum(x => x.Amount);
|
||||
try
|
||||
{
|
||||
_monthlyIncomeChange = Math.Round((MonthlyIncome / lastMonthIncome) - 1, 2);
|
||||
_monthlyExpensesChange = Math.Round((MonthlyExpenses / lastMonthExpenses) - 1, 2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(MonthlyIncomeChangeFormatted));
|
||||
OnPropertyChanged(nameof(MonthlyExpenseChangeFormatted));
|
||||
|
||||
UpdateSpendingByCategoryChart();
|
||||
_ = UpdateBudgetTracker();
|
||||
UpdateRecentTransactions();
|
||||
UpdateAccountsSummary();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -37,19 +120,94 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
mainViewModel.GoToTransactionsCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private void CreateTransaction()
|
||||
{
|
||||
|
||||
((MainViewModel)parentViewModel).OpenAddTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ChartData : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _name;
|
||||
[ObservableProperty] private double[] _values;
|
||||
[ObservableProperty] private SolidColorPaint _fill;
|
||||
private void UpdateSpendingByCategoryChart(ChartTimePeriod period = ChartTimePeriod.ThisMonth)
|
||||
{
|
||||
var tempList = new List<ColumnChartData>();
|
||||
|
||||
public Func<ChartPoint, string> ToolTipFormatter => point => $"${point.Coordinate.PrimaryValue:N0}";
|
||||
foreach (var category in Categories)
|
||||
{
|
||||
var categoryTransactions =
|
||||
Transactions.Where(x => x.CategoryId == category.Id && x.Type.Equals("expense", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
switch (period)
|
||||
{
|
||||
case ChartTimePeriod.ThisMonth:
|
||||
categoryTransactions = categoryTransactions.Where(x => x.Date.Month == DateTime.Now.Month);
|
||||
break;
|
||||
|
||||
case ChartTimePeriod.LastMonth:
|
||||
categoryTransactions = categoryTransactions.Where(x => x.Date.Month == DateTime.Now.AddMonths(-1).Month);
|
||||
break;
|
||||
|
||||
case ChartTimePeriod.ThisQuarter:
|
||||
categoryTransactions = categoryTransactions.Where(x =>
|
||||
x.Date.Month >= DateTime.Now.AddMonths(-(DateTime.Now.Month - 1) % 3).Month &&
|
||||
x.Date.Month <= DateTime.Now.AddMonths(-(DateTime.Now.Month - 1) % 3).AddMonths(3).Month);
|
||||
break;
|
||||
|
||||
case ChartTimePeriod.ThisYear:
|
||||
categoryTransactions = categoryTransactions.Where(x => x.Date.Year == DateTime.Now.Year);
|
||||
break;
|
||||
|
||||
default:
|
||||
categoryTransactions = categoryTransactions.Where(x => x.Date.Month == DateTime.Now.Month);
|
||||
break;
|
||||
}
|
||||
|
||||
var balance = categoryTransactions.Sum(x => x.Amount);
|
||||
if (balance == 0) continue;
|
||||
tempList.Add(new ColumnChartData()
|
||||
{ id = category.Id, Name = category.Name, Values = [(double)balance], Fill = new SolidColorPaint(SKColor.Parse(category.Color)) });
|
||||
}
|
||||
|
||||
tempList = tempList.OrderByDescending(x => x.Values[0]).ToList();
|
||||
SpendingByCategoryChartData = new ObservableCollection<ColumnChartData>(tempList);
|
||||
SpendingByCategoryChartSeries = tempList.Select(x => (ISeries)new ColumnSeries<double>
|
||||
{
|
||||
Name = x.Name,
|
||||
Values = x.Values,
|
||||
Fill = x.Fill,
|
||||
Padding = 4,
|
||||
MaxBarWidth = double.MaxValue
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private async Task UpdateBudgetTracker()
|
||||
{
|
||||
var budgets = await DataRepo.General.FetchProcessedBudgets(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1));
|
||||
BudgetsTrackerData = new ObservableCollection<Budget>(budgets.Where(x => !x.GroupHeader).OrderByDescending(x => x.PercentageUsed));
|
||||
}
|
||||
|
||||
private void UpdateRecentTransactions()
|
||||
{
|
||||
RecentTransactions = new ObservableCollection<Transaction>(Transactions.OrderByDescending(x => x.Date).Take(5));
|
||||
}
|
||||
|
||||
private void UpdateAccountsSummary()
|
||||
{
|
||||
foreach (var account in Accounts)
|
||||
{
|
||||
var accountTransactions = Transactions.Where(t => t.AccountId == account.Id).ToList();
|
||||
account.CurrentBalance = account.OpeningBalance + accountTransactions.Sum(t => t.Type == "income" ? t.Amount : -t.Amount);
|
||||
TotalNetworth += account.CurrentBalance;
|
||||
}
|
||||
|
||||
AccountsSummaryData = new ObservableCollection<Account>(Accounts.OrderBy(x => x.CreatedAt));
|
||||
OnPropertyChanged(nameof(AccountsSubtitle));
|
||||
}
|
||||
|
||||
private enum ChartTimePeriod
|
||||
{
|
||||
ThisMonth,
|
||||
LastMonth,
|
||||
ThisQuarter,
|
||||
ThisYear
|
||||
}
|
||||
}
|
||||
8
Clario/ViewModels/LoadingViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Clario.ViewModels;
|
||||
|
||||
public partial class LoadingViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Styling;
|
||||
using Clario.Data;
|
||||
using Clario.Models;
|
||||
using Clario.Models.GeneralModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -12,11 +17,19 @@ namespace Clario.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
public DashboardViewModel _dashboardViewModel;
|
||||
private DashboardViewModel _dashboardViewModel;
|
||||
public TransactionsViewModel _transactionsViewModel;
|
||||
public AccountsViewModel _accountsViewModel;
|
||||
public BudgetViewModel _budgetViewModel;
|
||||
private AccountsViewModel _accountsViewModel;
|
||||
private BudgetViewModel _budgetViewModel;
|
||||
[ObservableProperty] private TransactionFormViewModel _transactionFormViewModel;
|
||||
[ObservableProperty] public Profile? _profile;
|
||||
private List<Transaction> _transactions = new();
|
||||
private List<Category> _categories = new();
|
||||
private List<Budget> _budgets = new();
|
||||
private List<Account> _accounts = new();
|
||||
|
||||
[ObservableProperty] private bool _isTransactionFormVisible;
|
||||
|
||||
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(isOnDashboard), nameof(isOnTransactions), nameof(isOnAccounts), nameof(isOnBudget))]
|
||||
private ViewModelBase? _currentView;
|
||||
@@ -25,24 +38,162 @@ public partial class MainViewModel : ViewModelBase
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_dashboardViewModel = new DashboardViewModel() { parentViewModel = this };
|
||||
_transactionsViewModel = new TransactionsViewModel() { parentViewModel = this };
|
||||
_accountsViewModel = new AccountsViewModel() { parentViewModel = this };
|
||||
_budgetViewModel = new BudgetViewModel() { parentViewModel = this };
|
||||
CurrentView = _dashboardViewModel;
|
||||
// CurrentView = _transactionsViewModel;
|
||||
IsDarkTheme = ThemeService.IsDarkTheme;
|
||||
|
||||
Console.WriteLine("main vm loaded");
|
||||
CurrentView = new LoadingViewModel();
|
||||
_ = InitializeApp();
|
||||
}
|
||||
|
||||
|
||||
private async Task InitializeApp()
|
||||
{
|
||||
Profile = await DataRepo.General.FetchProfileInfo();
|
||||
_ = await DataRepo.General.FetchCategories();
|
||||
_ = await DataRepo.General.FetchAccounts();
|
||||
ThemeService.SwitchToTheme(Profile?.Theme ?? "system");
|
||||
try
|
||||
{
|
||||
var profilesTask = DataRepo.General.FetchProfileInfo();
|
||||
var categoriesTask = DataRepo.General.FetchCategories();
|
||||
var accountsTask = DataRepo.General.FetchAccounts();
|
||||
var transactionsTask = DataRepo.General.FetchTransactions();
|
||||
var budgetsTask = DataRepo.General.FetchBudgets();
|
||||
|
||||
await Task.WhenAll(profilesTask, categoriesTask, accountsTask, transactionsTask, budgetsTask);
|
||||
|
||||
Profile = profilesTask.Result;
|
||||
_categories = categoriesTask.Result;
|
||||
_accounts = accountsTask.Result;
|
||||
_transactions = transactionsTask.Result;
|
||||
_budgets = budgetsTask.Result;
|
||||
|
||||
Console.WriteLine("fetched all data");
|
||||
|
||||
_dashboardViewModel = new DashboardViewModel()
|
||||
{
|
||||
parentViewModel = this,
|
||||
Transactions = _transactions,
|
||||
Categories = _categories,
|
||||
Accounts = _accounts,
|
||||
Budgets = _budgets
|
||||
};
|
||||
_dashboardViewModel.initialize();
|
||||
CurrentView = _dashboardViewModel;
|
||||
|
||||
Console.WriteLine("initialized DashboardViewModel");
|
||||
_transactionsViewModel = new TransactionsViewModel()
|
||||
{
|
||||
parentViewModel = this,
|
||||
AllTransactions = _transactions.OrderByDescending(x => x.Date).ToList(),
|
||||
Categories = new ObservableCollection<Category>(_categories.OrderBy(x => x.CreatedAt)),
|
||||
Accounts = new ObservableCollection<Account>(_accounts.OrderBy(x => x.CreatedAt))
|
||||
};
|
||||
await _transactionsViewModel.Initialize();
|
||||
|
||||
Console.WriteLine("initialized TransactionsViewModel");
|
||||
_accountsViewModel = new AccountsViewModel()
|
||||
{
|
||||
parentViewModel = this,
|
||||
Accounts = _accounts,
|
||||
Transactions = _transactions
|
||||
};
|
||||
await _accountsViewModel.Initialize();
|
||||
|
||||
Console.WriteLine("initialized AccountsViewModel");
|
||||
_budgetViewModel = new BudgetViewModel()
|
||||
{
|
||||
parentViewModel = this,
|
||||
Profile = Profile,
|
||||
Budgets = _budgets,
|
||||
Categories = _categories,
|
||||
Transactions = _transactions
|
||||
};
|
||||
await _budgetViewModel.Initialize();
|
||||
Console.WriteLine("initialized BudgetViewModel");
|
||||
TransactionFormViewModel = new TransactionFormViewModel()
|
||||
{
|
||||
parentViewModel = this
|
||||
};
|
||||
Console.WriteLine("initialized TransactionFormViewModel");
|
||||
|
||||
IsDarkTheme = ThemeService.IsDarkTheme;
|
||||
|
||||
ThemeService.SwitchToTheme(Profile?.Theme ?? "system");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenAddTransaction()
|
||||
{
|
||||
if (IsTransactionFormVisible) return;
|
||||
TransactionFormViewModel.SetupForAdd(
|
||||
new ObservableCollection<Category>(_categories),
|
||||
new ObservableCollection<Account>(_accounts)
|
||||
);
|
||||
TransactionFormViewModel.OnSaved = () =>
|
||||
{
|
||||
if (TransactionFormViewModel.ResultTransaction is not null)
|
||||
{
|
||||
var previousItem = _transactionsViewModel.AllTransactions.First(x => x.Date < TransactionFormViewModel.ResultTransaction.Date);
|
||||
var index = _transactionsViewModel.AllTransactions.IndexOf(previousItem);
|
||||
if (index != -1)
|
||||
_transactionsViewModel.AllTransactions.Insert(index, TransactionFormViewModel.ResultTransaction);
|
||||
_transactionsViewModel.LoadPageCommand.Execute(1);
|
||||
}
|
||||
|
||||
CloseTransactionForm();
|
||||
};
|
||||
TransactionFormViewModel.OnCancelled = () => CloseTransactionForm();
|
||||
TransactionFormViewModel.OnDeleted = () =>
|
||||
{
|
||||
if (TransactionFormViewModel.ResultTransaction is { } resultTransaction)
|
||||
{
|
||||
_transactionsViewModel.AllTransactions.Remove(resultTransaction);
|
||||
_transactionsViewModel.LoadPageCommand.Execute(1);
|
||||
}
|
||||
|
||||
CloseTransactionForm();
|
||||
};
|
||||
IsTransactionFormVisible = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenEditTransaction(Transaction transaction)
|
||||
{
|
||||
TransactionFormViewModel.SetupForEdit(
|
||||
transaction,
|
||||
new ObservableCollection<Category>(_categories),
|
||||
new ObservableCollection<Account>(_accounts)
|
||||
);
|
||||
TransactionFormViewModel.OnSaved = () =>
|
||||
{
|
||||
if (TransactionFormViewModel.ResultTransaction is { } resultTransaction)
|
||||
{
|
||||
var index = _transactionsViewModel.AllTransactions.FindIndex(x => x.Id == transaction.Id);
|
||||
if (index != -1)
|
||||
_transactionsViewModel.AllTransactions[index] = resultTransaction;
|
||||
_transactionsViewModel.LoadPageCommand.Execute(1);
|
||||
}
|
||||
|
||||
CloseTransactionForm();
|
||||
};
|
||||
TransactionFormViewModel.OnCancelled = CloseTransactionForm;
|
||||
TransactionFormViewModel.OnDeleted = () =>
|
||||
{
|
||||
if (TransactionFormViewModel.ResultTransaction is { } resultTransaction)
|
||||
{
|
||||
_transactionsViewModel.AllTransactions.Remove(resultTransaction);
|
||||
_transactionsViewModel.LoadPageCommand.Execute(1);
|
||||
}
|
||||
|
||||
CloseTransactionForm();
|
||||
};
|
||||
IsTransactionFormVisible = true;
|
||||
}
|
||||
|
||||
|
||||
private void CloseTransactionForm()
|
||||
{
|
||||
IsTransactionFormVisible = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
233
Clario/ViewModels/TransactionFormViewModel.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Clario.Data;
|
||||
using Clario.Models;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Clario.ViewModels;
|
||||
|
||||
public partial class TransactionFormViewModel : ViewModelBase
|
||||
{
|
||||
public required ViewModelBase parentViewModel;
|
||||
|
||||
// ── Mode ────────────────────────────────────────────────
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(FormTitle), nameof(FormSubtitle), nameof(SaveButtonLabel))]
|
||||
private bool _isEditMode = false;
|
||||
|
||||
public string FormTitle => IsEditMode ? "Edit Transaction" : "New Transaction";
|
||||
public string FormSubtitle => IsEditMode ? "Update the details below" : "Fill in the details below";
|
||||
public string SaveButtonLabel => IsEditMode ? "Save Changes" : "Save Transaction";
|
||||
|
||||
// ── Fields ──────────────────────────────────────────────
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsExpense), nameof(IsIncome), nameof(IsValid))]
|
||||
private string _type = "expense";
|
||||
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
|
||||
private string _amount = "";
|
||||
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
|
||||
private string _description = "";
|
||||
|
||||
[ObservableProperty] private string? _note;
|
||||
[ObservableProperty] private List<DateTime> _dates = [DateTime.Now];
|
||||
[ObservableProperty] private string _currency = "USD";
|
||||
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
|
||||
private Category? _selectedCategory;
|
||||
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
|
||||
private Account? _selectedAccount;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<Category> _categories = new();
|
||||
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
|
||||
|
||||
// ── Validation ──────────────────────────────────────────
|
||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasError))]
|
||||
private string? _errorMessage;
|
||||
|
||||
public bool HasError => !string.IsNullOrEmpty(ErrorMessage);
|
||||
public bool IsExpense => Type == "expense";
|
||||
public bool IsIncome => Type == "income";
|
||||
|
||||
public bool IsValid =>
|
||||
decimal.TryParse(Amount, out var amt) && amt > 0 &&
|
||||
!string.IsNullOrWhiteSpace(Description) &&
|
||||
SelectedCategory is not null &&
|
||||
SelectedAccount is not null &&
|
||||
Dates is not null;
|
||||
|
||||
// ── Callbacks ───────────────────────────────────────────
|
||||
public Action? OnSaved;
|
||||
public Action? OnCancelled;
|
||||
public Action? OnDeleted;
|
||||
|
||||
// ── Edit mode: original transaction ─────────────────────
|
||||
private Transaction? _editingTransaction;
|
||||
private Guid? _editingId;
|
||||
|
||||
// ── Result transaction ──────────────────────────────────
|
||||
public Transaction? ResultTransaction { get; set; }
|
||||
|
||||
// ── Commands ────────────────────────────────────────────
|
||||
|
||||
[RelayCommand]
|
||||
private void SetType(string type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetToday()
|
||||
{
|
||||
Dates = [DateTime.Now];
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Save()
|
||||
{
|
||||
ErrorMessage = null;
|
||||
|
||||
if (!decimal.TryParse(Amount, out var amt) || amt <= 0)
|
||||
{
|
||||
ErrorMessage = "Please enter a valid amount.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
ErrorMessage = "Description is required.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedCategory is null)
|
||||
{
|
||||
ErrorMessage = "Please select a category.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedAccount is null)
|
||||
{
|
||||
ErrorMessage = "Please select an account.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (IsEditMode && _editingId.HasValue)
|
||||
{
|
||||
var updated = new Transaction
|
||||
{
|
||||
Id = _editingId.Value,
|
||||
UserId = Guid.Parse(Services.SupabaseService.Client.Auth.CurrentUser!.Id),
|
||||
Type = Type,
|
||||
Amount = amt,
|
||||
Description = Description.Trim(),
|
||||
Note = Note?.Trim(),
|
||||
Date = Dates.FirstOrDefault(),
|
||||
CategoryId = SelectedCategory.Id,
|
||||
AccountId = SelectedAccount.Id,
|
||||
Category = SelectedCategory,
|
||||
};
|
||||
await DataRepo.General.UpdateTransaction(updated);
|
||||
ResultTransaction = updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = Guid.Parse(Services.SupabaseService.Client.Auth.CurrentUser!.Id!),
|
||||
Type = Type,
|
||||
Amount = amt,
|
||||
Description = Description.Trim(),
|
||||
Note = Note?.Trim(),
|
||||
Date = Dates.FirstOrDefault(),
|
||||
CategoryId = SelectedCategory.Id,
|
||||
AccountId = SelectedAccount.Id,
|
||||
Category = SelectedCategory,
|
||||
};
|
||||
await DataRepo.General.InsertTransaction(transaction);
|
||||
ResultTransaction = transaction;
|
||||
}
|
||||
|
||||
OnSaved?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = "Something went wrong. Please try again.";
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Delete()
|
||||
{
|
||||
if (!IsEditMode || !_editingId.HasValue) return;
|
||||
|
||||
try
|
||||
{
|
||||
await DataRepo.General.DeleteTransaction(_editingId.Value);
|
||||
OnDeleted?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = "Failed to delete transaction.";
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel()
|
||||
{
|
||||
OnCancelled?.Invoke();
|
||||
}
|
||||
|
||||
// ── Public setup methods ─────────────────────────────────
|
||||
|
||||
/// <summary>Call this to open the form for adding a new transaction.</summary>
|
||||
public void SetupForAdd(
|
||||
ObservableCollection<Category> categories,
|
||||
ObservableCollection<Account> accounts)
|
||||
{
|
||||
IsEditMode = false;
|
||||
_editingId = null;
|
||||
Categories = categories;
|
||||
Accounts = accounts;
|
||||
Type = "expense";
|
||||
Amount = "";
|
||||
Description = "";
|
||||
Note = null;
|
||||
Dates = [DateTime.Now];
|
||||
ErrorMessage = null;
|
||||
SelectedCategory = categories.Count > 0 ? categories[0] : null;
|
||||
SelectedAccount = accounts.Count > 0 ? accounts[0] : null;
|
||||
ResultTransaction = null;
|
||||
}
|
||||
|
||||
/// <summary>Call this to open the form for editing an existing transaction.</summary>
|
||||
public void SetupForEdit(
|
||||
Transaction transaction,
|
||||
ObservableCollection<Category> categories,
|
||||
ObservableCollection<Account> accounts)
|
||||
{
|
||||
IsEditMode = true;
|
||||
_editingId = transaction.Id;
|
||||
Categories = categories;
|
||||
Accounts = accounts;
|
||||
Type = transaction.Type;
|
||||
Amount = transaction.Amount.ToString("0.00");
|
||||
Description = transaction.Description;
|
||||
Note = transaction.Note;
|
||||
Dates = [transaction.Date];
|
||||
ErrorMessage = null;
|
||||
SelectedCategory = categories.FirstOrDefault(c => c.Id == transaction.CategoryId)
|
||||
?? (categories.Count > 0 ? categories[0] : null);
|
||||
SelectedAccount = accounts.FirstOrDefault(a => a.Id == transaction.AccountId)
|
||||
?? (accounts.Count > 0 ? accounts[0] : null);
|
||||
ResultTransaction = transaction;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Clario.Data;
|
||||
using Clario.Messages;
|
||||
using Clario.Models;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel.__Internals;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
|
||||
@@ -18,8 +17,11 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
{
|
||||
public required ViewModelBase parentViewModel;
|
||||
|
||||
private List<Transaction> _allTransactions = new();
|
||||
private List<Transaction> _filteredTransactions = new();
|
||||
public List<Transaction> AllTransactions = new();
|
||||
[ObservableProperty] private ObservableCollection<Category> _categories = new();
|
||||
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
|
||||
|
||||
[ObservableProperty] private List<Transaction> _filteredTransactions = new();
|
||||
|
||||
private int _pageSize = 25;
|
||||
[ObservableProperty] private int _pageSizeIndex = 0;
|
||||
@@ -30,8 +32,6 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
[ObservableProperty] private string _paginationSummaryText;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<Transaction> _pagedTransactions = new();
|
||||
[ObservableProperty] private ObservableCollection<Category> _categories = new();
|
||||
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
|
||||
|
||||
[ObservableProperty] private ObservableCollection<string> _sortOptions = new()
|
||||
{
|
||||
@@ -84,7 +84,6 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
|
||||
public TransactionsViewModel()
|
||||
{
|
||||
_ = Initialize();
|
||||
}
|
||||
|
||||
partial void OnPageSizeIndexChanged(int value)
|
||||
@@ -141,6 +140,7 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
GroupTransactions();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ApplyFilters()
|
||||
{
|
||||
// Console.WriteLine($"Search Text: {_searchText}");
|
||||
@@ -149,7 +149,7 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
// Console.WriteLine($"Transaction Type: {_transactionType}");
|
||||
|
||||
|
||||
var filtered = _allTransactions.Where(x =>
|
||||
var filtered = AllTransactions.Where(x =>
|
||||
x.Description.Contains(_searchText, StringComparison.OrdinalIgnoreCase)
|
||||
|| x.Note.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -232,7 +232,7 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
|
||||
_filteredTransactions = filtered.ToList();
|
||||
FilteredTransactions = filtered.ToList();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -271,14 +271,25 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
|
||||
private void GroupTransactions()
|
||||
{
|
||||
var dates = PagedTransactions.Select(x => x.Date).Distinct().ToList();
|
||||
var ToRemove = PagedTransactions.Where(x => x.GroupHeader).ToList();
|
||||
foreach (var item in ToRemove)
|
||||
{
|
||||
PagedTransactions.Remove(item);
|
||||
}
|
||||
|
||||
var dates = PagedTransactions
|
||||
.Where(x => !x.GroupHeader)
|
||||
.Select(x => x.Date.Date) // strip time
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var date in dates)
|
||||
{
|
||||
var index = PagedTransactions.IndexOf(PagedTransactions.First(x => x.Date == date));
|
||||
var index = PagedTransactions.IndexOf(PagedTransactions.First(x => x.Date.Date == date && !x.GroupHeader));
|
||||
string label;
|
||||
var culture = new CultureInfo("en-US");
|
||||
if (date.Day == DateTime.Now.Day) label = "Today - " + date.ToString("MMM dd", culture);
|
||||
else if (date.Day == DateTime.Now.AddDays(-1).Day) label = "Yesterday - " + date.ToString("MMM dd", culture);
|
||||
if (date.Date == DateTime.Now.Date) label = "Today - " + date.ToString("MMM dd", culture);
|
||||
else if (date.Date == DateTime.Now.AddDays(-1).Date) label = "Yesterday - " + date.ToString("MMM dd", culture);
|
||||
else label = date.ToString("MMM dd, yyyy", culture);
|
||||
var header = new Transaction { Description = label, Date = date, GroupHeader = true };
|
||||
|
||||
@@ -286,16 +297,12 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Initialize()
|
||||
public async Task Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
await FetchAndInitializeCategories();
|
||||
await FetchAndInitializeAccounts();
|
||||
|
||||
|
||||
var transactions = await DataRepo.General.FetchTransactions();
|
||||
_allTransactions = transactions.OrderByDescending(x => x.Date).ToList();
|
||||
InitializeCategories();
|
||||
InitializeAccounts();
|
||||
|
||||
CalculateMonthlyFinancials();
|
||||
|
||||
@@ -310,28 +317,24 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchAndInitializeCategories()
|
||||
private void InitializeCategories()
|
||||
{
|
||||
var categories = await DataRepo.General.FetchCategories();
|
||||
Categories = new ObservableCollection<Category>(categories.OrderBy(x => x.CreatedAt));
|
||||
Categories.Insert(0, new Category() { Name = "All Categories" });
|
||||
SelectedCategory = Categories.First();
|
||||
}
|
||||
|
||||
private async Task FetchAndInitializeAccounts()
|
||||
private void InitializeAccounts()
|
||||
{
|
||||
var accounts = await DataRepo.General.FetchAccounts();
|
||||
Accounts = new ObservableCollection<Account>(accounts.OrderBy(x => x.CreatedAt));
|
||||
Accounts.Insert(0, new Account() { Name = "All Accounts" });
|
||||
SelectedAccount = Accounts.First();
|
||||
}
|
||||
|
||||
private void CalculateMonthlyFinancials()
|
||||
{
|
||||
TotalExpenses = _allTransactions.Where(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
|
||||
TotalIncome = _allTransactions.Where(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
|
||||
ExpensesCount = _allTransactions.Count(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month);
|
||||
IncomeCount = _allTransactions.Count(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month);
|
||||
TotalExpenses = AllTransactions.Where(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
|
||||
TotalIncome = AllTransactions.Where(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
|
||||
ExpensesCount = AllTransactions.Count(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month);
|
||||
IncomeCount = AllTransactions.Count(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month);
|
||||
}
|
||||
|
||||
public static List<T> GetSurrounding<T>(List<T> list, T item, int count = 5)
|
||||
@@ -348,4 +351,16 @@ public partial class TransactionsViewModel : ViewModelBase
|
||||
|
||||
return list.GetRange(start, end - start);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CreateTransaction()
|
||||
{
|
||||
((MainViewModel)parentViewModel).OpenAddTransaction();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void EditTransaction(Transaction transaction)
|
||||
{
|
||||
((MainViewModel)parentViewModel).OpenEditTransaction(transaction);
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@
|
||||
Foreground="{Binding Type,Converter={StaticResource AmountColorConverter}}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource AmountSignConverter}">
|
||||
<MultiBinding Converter="{StaticResource AmountSignConverter}" ConverterParameter="round">
|
||||
<Binding Path="Amount" />
|
||||
<Binding Path="Type" />
|
||||
</MultiBinding>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
x:Class="Clario.Views.AuthView">
|
||||
<Grid>
|
||||
|
||||
<!-- ── Background ────────────────────────── -->
|
||||
<Border Background="{DynamicResource BgBase}" />
|
||||
<!-- Background -->
|
||||
<!-- <Calendar SelectionMode="SingleRange"> -->
|
||||
<!-- </Calendar> -->
|
||||
|
||||
<!-- ── Subtle grid pattern overlay ──────── -->
|
||||
<!-- <Border Opacity="0.03" Background="{DynamicResource BgBase}"> -->
|
||||
<!-- -->
|
||||
|
||||
|
||||
<!-- <Border Background="{DynamicResource AccentBlue}" VerticalAlignment="Top" HorizontalAlignment="Left" Height="400" Width="400" Padding="10"> -->
|
||||
<!-- -->
|
||||
<!-- </Border> -->
|
||||
|
||||
<!-- ── Center card ───────────────────────── -->
|
||||
<!-- Center card -->
|
||||
<Border HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
@@ -31,7 +30,7 @@
|
||||
BoxShadow="0 24 64 0 #40000000">
|
||||
<StackPanel Spacing="0">
|
||||
|
||||
<!-- ── Logo + App name ─────────────── -->
|
||||
<!-- Logo + App name -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Spacing="0"
|
||||
Margin="0,0,0,32">
|
||||
@@ -39,7 +38,7 @@
|
||||
CornerRadius="16"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center">
|
||||
<Image Source="../Assets/Logo textmark.png"></Image>
|
||||
<!-- <Image Source="../Assets/Logo textmark.png"></Image> -->
|
||||
</Border>
|
||||
<!-- REPLACE: app name -->
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
@@ -55,7 +54,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ── Tab switcher ────────────────── -->
|
||||
<!-- Tab switcher -->
|
||||
<Border Background="{DynamicResource BgBase}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
@@ -433,7 +432,7 @@
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<!-- ── Footer ──────────────────────── -->
|
||||
<!-- Footer -->
|
||||
<Separator Margin="0,0,0,16" />
|
||||
<TextBlock Text="Your data is encrypted and synced securely."
|
||||
FontSize="11"
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:views="clr-namespace:Clario.Views"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:skiaSharpView="clr-namespace:LiveChartsCore.SkiaSharpView;assembly=LiveChartsCore.SkiaSharpView"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
x:DataType="vm:BudgetViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="1180" d:DesignHeight="800"
|
||||
x:Class="Clario.Views.BudgetView">
|
||||
@@ -28,7 +29,7 @@
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<!-- REPLACE: bind to CurrentPeriodLabel e.g. "March 2026" -->
|
||||
<TextBlock Text="March 2026"
|
||||
<TextBlock Text="{Binding CurrentPeriodFormatted}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="Budget"
|
||||
@@ -51,15 +52,17 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!-- REPLACE: Command="{Binding PreviousPeriodCommand}" -->
|
||||
<Button Background="Transparent"
|
||||
Classes="nav textless"
|
||||
BorderThickness="0"
|
||||
Padding="10,8"
|
||||
Cursor="Hand">
|
||||
Cursor="Hand"
|
||||
Command="{Binding PreviousPeriodCommand}">
|
||||
<Svg Path="../Assets/Icons/chevron-left.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
<!-- REPLACE: bind to CurrentPeriodLabel -->
|
||||
<TextBlock Text="Mar 2026"
|
||||
<TextBlock Text="{Binding CurrentPeriodFormatted}"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
@@ -67,9 +70,11 @@
|
||||
Margin="4,0" />
|
||||
<!-- REPLACE: Command="{Binding NextPeriodCommand}" -->
|
||||
<Button Background="Transparent"
|
||||
Classes="nav textless"
|
||||
BorderThickness="0"
|
||||
Padding="10,8"
|
||||
Cursor="Hand">
|
||||
Cursor="Hand"
|
||||
Command="{Binding NextPeriodCommand}">
|
||||
<Svg Path="../Assets/Icons/chevron-right.svg"
|
||||
Width="14" Height="14"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
@@ -116,365 +121,139 @@
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Margin="0,0,12,0" Padding="0 0 8 0">
|
||||
<StackPanel Spacing="12" Margin="0 0 0 28">
|
||||
|
||||
<!-- ── On Track ───────────────────────── -->
|
||||
<TextBlock Text="ON TRACK"
|
||||
Classes="label"
|
||||
Margin="0,0,0,4" />
|
||||
|
||||
<!-- REPLACE: ItemsSource="{Binding OnTrackBudgets}" with DataTemplate -->
|
||||
|
||||
<!-- Budget Card — Food (healthy) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
|
||||
<!-- Header row -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource IconBgGreen}"
|
||||
CornerRadius="10"
|
||||
Width="40" Height="40"
|
||||
Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/utensils.svg"
|
||||
Width="18" Height="18"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<!-- REPLACE: bind to Budget.CategoryName -->
|
||||
<TextBlock Text="Food & Dining"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<!-- REPLACE: bind to Budget.TransactionCount -->
|
||||
<TextBlock Text="8 transactions"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<!-- Spent / Limit -->
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2"
|
||||
Margin="0,0,12,0">
|
||||
<!-- REPLACE: bind to Budget.SpentFormatted -->
|
||||
<TextBlock Text="$340"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<!-- REPLACE: bind to Budget.LimitFormatted -->
|
||||
<TextBlock Text="of $500"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<!-- Edit button -->
|
||||
<!-- REPLACE: Command="{Binding EditBudgetCommand}" CommandParameter="{Binding}" -->
|
||||
<Button Grid.Column="3"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="4"
|
||||
VerticalAlignment="Center"
|
||||
<ItemsControl ItemsSource="{Binding VisibleBudgets}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="12" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<Panel>
|
||||
<TextBlock IsVisible="{Binding GroupHeader}"
|
||||
Text="{Binding Category.Name}"
|
||||
Classes="label"
|
||||
Margin="0,0,0,4" />
|
||||
<Border IsVisible="{Binding !GroupHeader}"
|
||||
Classes.budget-card="{Binding IsOnTrack}"
|
||||
Classes.budget-card-warning="{Binding IsWarning}"
|
||||
Classes.budget-card-over="{Binding IsOverBudget}"
|
||||
Background="{DynamicResource BgSurface}"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg"
|
||||
Width="15" Height="15"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="14">
|
||||
<!-- Header row -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="10"
|
||||
Width="40" Height="40"
|
||||
Margin="0,0,14,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color,Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||
Width="18" Height="18"
|
||||
Css="{Binding Category.Color,Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<!-- REPLACE: bind to Budget.CategoryName -->
|
||||
<TextBlock Text="{Binding Category.Name}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<!-- REPLACE: bind to Budget.TransactionCount -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding TransactionsCount}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" transactions"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Spent / Limit -->
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2"
|
||||
Margin="0,0,12,0">
|
||||
<!-- REPLACE: bind to Budget.SpentFormatted -->
|
||||
<TextBlock Text="{Binding SpentFormatted}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<!-- REPLACE: bind to Budget.LimitFormatted -->
|
||||
<TextBlock Text="{Binding AmountFormatted}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<!-- Edit button -->
|
||||
<!-- REPLACE: Command="{Binding EditBudgetCommand}" CommandParameter="{Binding}" -->
|
||||
<Button Grid.Column="3"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="4"
|
||||
VerticalAlignment="Center"
|
||||
Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg"
|
||||
Width="15" Height="15"
|
||||
Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Progress bar + remaining -->
|
||||
<StackPanel Spacing="6">
|
||||
<!-- REPLACE: Value="{Binding Budget.PercentageUsed}" -->
|
||||
<ProgressBar Classes.green="{Binding IsOnTrack}"
|
||||
Classes.yellow="{Binding IsWarning}"
|
||||
Classes.red="{Binding IsOverBudget}"
|
||||
Value="{Binding Spent}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding LimitAmount}"
|
||||
Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes.badge-green="{Binding IsOnTrack}"
|
||||
Classes.badge-warning="{Binding IsWarning}"
|
||||
Classes.badge-over="{Binding IsOverBudget}"
|
||||
CornerRadius="20"
|
||||
Padding="6,2">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/triangle-alert.svg" Css="{DynamicResource SvgYellow}"
|
||||
IsVisible="{Binding IsWarning}" Height="13" VerticalAlignment="Center" />
|
||||
<Svg Path="../Assets/Icons/circle-alert.svg" Css="{DynamicResource SvgRed}"
|
||||
IsVisible="{Binding IsOverBudget}" Height="13" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding PercentageFormatted}"
|
||||
FontSize="11" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!-- REPLACE: bind to Budget.RemainingFormatted -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding RemainingFormatted}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Progress bar + remaining -->
|
||||
<StackPanel Spacing="6">
|
||||
<!-- REPLACE: Value="{Binding Budget.PercentageUsed}" -->
|
||||
<ProgressBar Classes="green"
|
||||
Value="68"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6">
|
||||
<Border Background="{DynamicResource IconBgGreen}"
|
||||
CornerRadius="20"
|
||||
Padding="6,2">
|
||||
<TextBlock Text="68% used"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource AccentGreen}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!-- REPLACE: bind to Budget.RemainingFormatted -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="$160 left"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Budget Card — Transport (healthy) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgBlue}" CornerRadius="10" Width="40" Height="40" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/car.svg" Width="18" Height="18" Css="{DynamicResource SvgBlue}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Transport" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="3 transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="2" Margin="0,0,12,0">
|
||||
<TextBlock Text="$110" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<TextBlock Text="of $200" FontSize="11" Foreground="{DynamicResource TextMuted}" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Background="Transparent" BorderThickness="0" Padding="4" VerticalAlignment="Center" Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg" Width="15" Height="15" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<ProgressBar Classes="green" Value="55" Minimum="0" Maximum="100" Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="20" Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="55% used" FontSize="11" Foreground="{DynamicResource AccentGreen}" />
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Text="$90 left" FontSize="11" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Budget Card — Health (healthy) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource BorderSubtle}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgPink}" CornerRadius="10" Width="40" Height="40" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/heart-pulse.svg" Width="18" Height="18" Css="{DynamicResource SvgPink}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Health" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="2 transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="2" Margin="0,0,12,0">
|
||||
<TextBlock Text="$69" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource TextPrimary}"
|
||||
HorizontalAlignment="Right" />
|
||||
<TextBlock Text="of $150" FontSize="11" Foreground="{DynamicResource TextMuted}" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Background="Transparent" BorderThickness="0" Padding="4" VerticalAlignment="Center" Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg" Width="15" Height="15" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<ProgressBar Classes="green" Value="46" Minimum="0" Maximum="100" Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="20" Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="46% used" FontSize="11" Foreground="{DynamicResource AccentGreen}" />
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Text="$81 left" FontSize="11" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Approaching Limit ───────────────── -->
|
||||
<TextBlock Text="APPROACHING LIMIT"
|
||||
Classes="label"
|
||||
Margin="0,12,0,4" />
|
||||
|
||||
<!-- REPLACE: ItemsSource="{Binding WarningBudgets}" with DataTemplate -->
|
||||
|
||||
<!-- Budget Card — Leisure (warning ~85%) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource AccentYellow}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgPurple}" CornerRadius="10" Width="40" Height="40" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/gamepad-2.svg" Width="18" Height="18" Css="{DynamicResource SvgPurple}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Entertainment" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="5 transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="2" Margin="0,0,12,0">
|
||||
<TextBlock Text="$170" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource AccentYellow}"
|
||||
HorizontalAlignment="Right" />
|
||||
<TextBlock Text="of $200" FontSize="11" Foreground="{DynamicResource TextMuted}" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Background="Transparent" BorderThickness="0" Padding="4" VerticalAlignment="Center" Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg" Width="15" Height="15" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<ProgressBar Classes="yellow" Value="85" Minimum="0" Maximum="100" Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource BadgeBgYellow}" CornerRadius="20" Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<Svg Path="../Assets/Icons/triangle-alert.svg" Width="11" Height="11"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #F5C842; }" />
|
||||
<TextBlock Text="85% used" FontSize="11" Foreground="{DynamicResource AccentYellow}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Text="$30 left" FontSize="11" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Budget Card — Housing (warning ~90%) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource AccentYellow}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgOrange}" CornerRadius="10" Width="40" Height="40" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/house.svg" Width="18" Height="18" Css="{DynamicResource SvgOrange}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Housing" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="4 transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="2" Margin="0,0,12,0">
|
||||
<TextBlock Text="$540" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource AccentYellow}"
|
||||
HorizontalAlignment="Right" />
|
||||
<TextBlock Text="of $600" FontSize="11" Foreground="{DynamicResource TextMuted}" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Background="Transparent" BorderThickness="0" Padding="4" VerticalAlignment="Center" Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg" Width="15" Height="15" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<ProgressBar Classes="yellow" Value="90" Minimum="0" Maximum="100" Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource BadgeBgYellow}" CornerRadius="20" Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<Svg Path="../Assets/Icons/triangle-alert.svg" Width="11" Height="11"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #F5C842; }" />
|
||||
<TextBlock Text="90% used" FontSize="11" Foreground="{DynamicResource AccentYellow}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Text="$60 left" FontSize="11" Foreground="{DynamicResource TextMuted}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ── Over Budget ────────────────────── -->
|
||||
<TextBlock Text="OVER BUDGET"
|
||||
Classes="label"
|
||||
Margin="0,12,0,4" />
|
||||
|
||||
<!-- REPLACE: ItemsSource="{Binding OverBudgets}" with DataTemplate -->
|
||||
|
||||
<!-- Budget Card — Other (over budget) -->
|
||||
<Border Background="{DynamicResource BgSurface}"
|
||||
BorderBrush="{DynamicResource AccentRed}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
Padding="20"
|
||||
Cursor="Hand">
|
||||
<StackPanel Spacing="14">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgRed}" CornerRadius="10" Width="40" Height="40" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/shopping-bag.svg" Width="18" Height="18" Css="{DynamicResource SvgRed}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Shopping" FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Text="7 transactions" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="2" Margin="0,0,12,0">
|
||||
<TextBlock Text="$380" FontSize="14" FontWeight="Bold" Foreground="{DynamicResource AccentRed}" HorizontalAlignment="Right" />
|
||||
<TextBlock Text="of $300" FontSize="11" Foreground="{DynamicResource TextMuted}" HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Background="Transparent" BorderThickness="0" Padding="4" VerticalAlignment="Center" Cursor="Hand">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight"
|
||||
FlyoutPresenterTheme="{StaticResource TransparentFlyoutPresenter}">
|
||||
<views:BudgetCardMenuView />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Svg Path="../Assets/Icons/ellipsis.svg" Width="15" Height="15" Css="{DynamicResource SvgMuted}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Spacing="6">
|
||||
<!-- Clamped to 100 visually, real % shown in badge -->
|
||||
<ProgressBar Classes="red" Value="100" Minimum="0" Maximum="100" Height="6" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource BadgeBgRed}" CornerRadius="20" Padding="6,2"
|
||||
HorizontalAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<Svg Path="../Assets/Icons/circle-alert.svg" Width="11" Height="11"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
|
||||
<TextBlock Text="$80 over budget" FontSize="11" Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Text="127%" FontSize="11" Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ─────────────────────────────────────
|
||||
@@ -503,22 +282,25 @@
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Total Budgeted" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to TotalBudgetedFormatted -->
|
||||
<TextBlock Grid.Column="1" Text="$1,950" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalBudgeted, StringFormat='$0'}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Total Spent" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to TotalSpentFormatted -->
|
||||
<TextBlock Grid.Column="1" Text="$1,609" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalSpent, StringFormat='$0'}" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Overall progress bar -->
|
||||
<StackPanel Spacing="5" Margin="0,4,0,0">
|
||||
<!-- REPLACE: Value="{Binding OverallPercentageUsed}" -->
|
||||
<ProgressBar Classes="green" Value="82" Minimum="0" Maximum="100" Height="8" />
|
||||
<ProgressBar Classes="green" Value="{Binding TotalSpent}" Minimum="0" Maximum="{Binding TotalBudgeted}" Height="8" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="82% of total budget" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Grid.Column="0" Text="{Binding SpentPercentageFormatted}" FontSize="11"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to TotalRemainingFormatted -->
|
||||
<TextBlock Grid.Column="1" Text="$341 left" FontSize="11" Foreground="{DynamicResource AccentGreen}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalLeftFormatted}" FontSize="11" Foreground="{DynamicResource AccentGreen}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
@@ -540,7 +322,7 @@
|
||||
<TextBlock Grid.Column="1" Text="On track" FontSize="13" Foreground="{DynamicResource TextSecondary}"
|
||||
VerticalAlignment="Center" />
|
||||
<!-- REPLACE: bind to OnTrackCount -->
|
||||
<TextBlock Grid.Column="2" Text="3 budgets" FontSize="12" Foreground="{DynamicResource AccentGreen}"
|
||||
<TextBlock Grid.Column="2" Text="{Binding OnTrackCountFormatted}" FontSize="12" Foreground="{DynamicResource AccentGreen}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
@@ -557,7 +339,8 @@
|
||||
<TextBlock Grid.Column="1" Text="Approaching limit" FontSize="13" Foreground="{DynamicResource TextSecondary}"
|
||||
VerticalAlignment="Center" />
|
||||
<!-- REPLACE: bind to WarningCount -->
|
||||
<TextBlock Grid.Column="2" Text="2 budgets" FontSize="12" Foreground="{DynamicResource AccentYellow}"
|
||||
<TextBlock Grid.Column="2" Text="{Binding ApproachingCountFormatted}" FontSize="12"
|
||||
Foreground="{DynamicResource AccentYellow}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
@@ -574,7 +357,7 @@
|
||||
<TextBlock Grid.Column="1" Text="Over budget" FontSize="13" Foreground="{DynamicResource TextSecondary}"
|
||||
VerticalAlignment="Center" />
|
||||
<!-- REPLACE: bind to OverBudgetCount -->
|
||||
<TextBlock Grid.Column="2" Text="1 budget" FontSize="12" Foreground="{DynamicResource AccentRed}"
|
||||
<TextBlock Grid.Column="2" Text="{Binding OverBudgetCountFormatted}" FontSize="12" Foreground="{DynamicResource AccentRed}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
@@ -602,17 +385,23 @@
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="Period Progress" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<!-- REPLACE: bind to PeriodProgressLabel e.g. "22 of 31 days" -->
|
||||
<TextBlock Text="22 of 31 days elapsed" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding PeriodDaysPassed}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" of " FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding PeriodLength}" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" days elapsed" FontSize="11" Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- REPLACE: Value="{Binding PeriodDayPercentage}" -->
|
||||
<ProgressBar Classes="blue" Value="71" Minimum="0" Maximum="100" Height="6" />
|
||||
<ProgressBar Classes="blue" Value="{Binding PeriodDaysPassed}" Minimum="0" Maximum="{Binding PeriodLength}" Height="6" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Days remaining" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to DaysRemainingLabel -->
|
||||
<TextBlock Grid.Column="1" Text="9 days" FontSize="12" FontWeight="SemiBold" Foreground="{DynamicResource AccentBlue}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding PeriodDaysLeftFormatted}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentBlue}" />
|
||||
</Grid>
|
||||
|
||||
<Separator />
|
||||
@@ -625,7 +414,7 @@
|
||||
</StackPanel>
|
||||
<!-- REPLACE: bind to DailyRemainingFormatted -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="$37.90"
|
||||
Text="{Binding DailyBudgetLeftFormatted}"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}"
|
||||
@@ -649,30 +438,22 @@
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
|
||||
<Border Height="150" ClipToBounds="True">
|
||||
<lvc:PieChart SeriesSource="{Binding SpendingBreakdown}" Height="300" LegendPosition="Hidden" InitialRotation="-180"
|
||||
<lvc:PieChart Series="{Binding SpendingBreakdownChartSeries}" Height="300" LegendPosition="Hidden" InitialRotation="-180"
|
||||
MaxAngle="180" FlowDirection="LeftToRight" VerticalAlignment="Top" Background="{DynamicResource BgSurface}">
|
||||
<lvc:PieChart.SeriesTemplate>
|
||||
<DataTemplate x:DataType="vm:PieData">
|
||||
<lvc:XamlPieSeries SeriesName="{Binding Name}"
|
||||
Values="{Binding Values}"
|
||||
Fill="{Binding Fill}"
|
||||
InnerRadius="{Binding InnerRadius}"
|
||||
ToolTipLabelFormatter="{Binding Formatter}" />
|
||||
</DataTemplate>
|
||||
</lvc:PieChart.SeriesTemplate>
|
||||
</lvc:PieChart>
|
||||
</Border>
|
||||
<ItemsControl ItemsSource="{Binding SpendingBreakdown}" Margin="0 16 0 0">
|
||||
<ItemsControl ItemsSource="{Binding SpendingBreakdownLegends}" Margin="0 16 0 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel ItemSpacing="16" />
|
||||
<WrapPanel ItemSpacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:PieData">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<Border Height="10" Width="10" CornerRadius="5" Background="{Binding Bg}" />
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" IsVisible="{Binding !GroupHeader }" Margin="0 0 8 0">
|
||||
<Border Height="10" Width="10" CornerRadius="5"
|
||||
Background="{Binding Category.Color,Converter={StaticResource HexToColorConverter},ConverterParameter='brush'}" />
|
||||
<TextBlock Text="{Binding Category.Name}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
@@ -788,30 +569,32 @@
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Monthly goal" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to SavingsGoalFormatted -->
|
||||
<TextBlock Grid.Column="1" Text="$500" FontSize="12" FontWeight="SemiBold" Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Profile.SavingsGoal, StringFormat='$0'}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Projected savings" FontSize="12" Foreground="{DynamicResource TextMuted}" />
|
||||
<!-- REPLACE: bind to ProjectedSavingsFormatted -->
|
||||
<TextBlock Grid.Column="1" Text="$341" FontSize="12" FontWeight="SemiBold" Foreground="{DynamicResource AccentYellow}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalLeftFormatted}" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentYellow}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- REPLACE: Value="{Binding SavingsGoalPercentage}" -->
|
||||
<ProgressBar Classes="yellow" Value="68" Minimum="0" Maximum="100" Height="6" />
|
||||
<ProgressBar Classes="yellow" Value="{Binding TotalLeft}" Minimum="0" Maximum="{Binding Profile.SavingsGoal}" Height="6" />
|
||||
|
||||
<Border Background="{DynamicResource BadgeBgYellow}"
|
||||
CornerRadius="10"
|
||||
Padding="12,8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Svg Path="../Assets/Icons/info.svg" Width="14" Height="14"
|
||||
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="8">
|
||||
<Svg Grid.Column="0" Path="../Assets/Icons/info.svg" Width="14" Height="14"
|
||||
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #F5C842; }" />
|
||||
<TextBlock Text="Reduce spending by $159 to hit your goal"
|
||||
<TextBlock Grid.Column="1" Text="{Binding SavingsHint}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource AccentYellow}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Clario.ViewModels"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:model="clr-namespace:Clario.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="1180" d:DesignHeight="800"
|
||||
MinWidth="780" MinHeight="600"
|
||||
x:DataType="vm:DashboardViewModel"
|
||||
@@ -12,11 +13,9 @@
|
||||
<vm:DashboardViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid ColumnDefinitions="*">
|
||||
|
||||
<!-- ───────────────────────────────────── MAIN CONTENT ───────────────────────────────────── -->
|
||||
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Name="mainScrollviewer">
|
||||
<StackPanel Spacing="24" Margin="32,28,32,32">
|
||||
<!-- ── Top Bar ─────────────────────── -->
|
||||
<!-- Top Bar -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Classes="muted" Text="Friday, March 6, 2026" />
|
||||
@@ -28,18 +27,16 @@
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1" Padding="14,8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Svg Path="../Assets/Icons/calendar-days.svg" Height="13" Width="13" />
|
||||
|
||||
<TextBlock Text="Jan – Mar 2026" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextSecondary}" VerticalAlignment="Center" />
|
||||
<TextBlock Text="Jan – Mar 2026" FontSize="{StaticResource FontSizeBody}" Foreground="{DynamicResource TextSecondary}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button Background="{DynamicResource AccentBlue}" Foreground="{DynamicResource BgBase}"
|
||||
FontWeight="SemiBold" FontSize="{StaticResource FontSizeBody}" CornerRadius="{StaticResource RadiusControl}" Padding="16,8"
|
||||
BorderThickness="0" Cursor="Hand" Content="+ Add Transaction" />
|
||||
|
||||
<Button Background="{DynamicResource AccentBlue}" Foreground="{DynamicResource BgBase}" FontWeight="SemiBold"
|
||||
FontSize="{StaticResource FontSizeBody}" CornerRadius="{StaticResource RadiusControl}" Padding="16,8" BorderThickness="0"
|
||||
Cursor="Hand" Content="+ Add Transaction" Command="{Binding CreateTransactionCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- ── KPI Cards Row ───────────────── -->
|
||||
<!-- KPI Cards Row -->
|
||||
<Grid ColumnDefinitions="*,*,*" HorizontalAlignment="Stretch" MaxHeight="160">
|
||||
<Grid.Styles>
|
||||
<Style Selector="Grid > Border">
|
||||
@@ -51,17 +48,15 @@
|
||||
<StackPanel Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border Background="{DynamicResource IconBgGreen}" CornerRadius="{StaticResource RadiusIcon}" Padding="7">
|
||||
<Svg Path="../Assets/Icons/trending-up.svg" Height="14" Width="14"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
<Svg Path="../Assets/Icons/trending-up.svg" Height="14" Width="14" Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<TextBlock Classes="label" Text="MONTHLY INCOME" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="$6,850.00" FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold"
|
||||
<TextBlock Text="{Binding MonthlyIncome, StringFormat='$0.00'}" FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes="badge-green">
|
||||
<TextBlock Text="↑ 3.2%"
|
||||
FontSize="{StaticResource FontSizeLabel}" FontWeight="SemiBold"
|
||||
<TextBlock Text="{Binding MonthlyIncomeChangeFormatted}" FontSize="{StaticResource FontSizeLabel}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentGreen}" />
|
||||
</Border>
|
||||
<TextBlock Classes="muted" Text="vs last month" VerticalAlignment="Center" />
|
||||
@@ -73,16 +68,15 @@
|
||||
<StackPanel Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border Background="{DynamicResource IconBgOrange}" CornerRadius="{StaticResource RadiusIcon}" Padding="7">
|
||||
<Svg Path="../Assets/Icons/trending-down.svg" Height="14" Width="14"
|
||||
Css="{DynamicResource SvgRed}" />
|
||||
<Svg Path="../Assets/Icons/trending-down.svg" Height="14" Width="14" Css="{DynamicResource SvgRed}" />
|
||||
</Border>
|
||||
<TextBlock Classes="label" Text="MONTHLY EXPENSES" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="$3,240.00" FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold"
|
||||
<TextBlock Text="{Binding MonthlyExpenses, StringFormat='$0.00'}" FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes="badge-red">
|
||||
<TextBlock Text="↑ 5.1%" FontSize="{StaticResource FontSizeLabel}" FontWeight="SemiBold"
|
||||
<TextBlock Text="{Binding MonthlyExpenseChangeFormatted}" FontSize="{StaticResource FontSizeLabel}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</Border>
|
||||
<TextBlock Classes="muted" Text="vs last month" VerticalAlignment="Center" />
|
||||
@@ -94,21 +88,25 @@
|
||||
<StackPanel Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Border Background="{DynamicResource IconBgPurple}" CornerRadius="{StaticResource RadiusIcon}" Padding="7">
|
||||
<Svg Path="../Assets/Icons/landmark.svg" Height="14" Width="14"
|
||||
Css="{DynamicResource SvgPurple}" />
|
||||
<Svg Path="../Assets/Icons/landmark.svg" Height="14" Width="14" Css="{DynamicResource SvgPurple}" />
|
||||
</Border>
|
||||
<TextBlock Classes="label" Text="SAVINGS RATE" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="52.7%" FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<ProgressBar Classes="green" Minimum="0" Maximum="100" Value="52.7" />
|
||||
|
||||
<TextBlock FontSize="{StaticResource FontSizePageTitle}" FontWeight="Bold" Foreground="{DynamicResource TextPrimary}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource PercentageConverter}">
|
||||
<Binding Path="MonthlyExpenses" /> <Binding Path="MonthlyIncome" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<ProgressBar Classes="green" Minimum="0" Maximum="{Binding MonthlyIncome}" Value="{Binding MonthlyExpenses}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- ── Mid Row: Spending Chart + Budget ─ -->
|
||||
<!-- Mid Row: Spending Chart + Budget -->
|
||||
|
||||
<Grid ColumnDefinitions="*,340" MaxHeight="470">
|
||||
<!-- Spending Breakdown (placeholder chart area) -->
|
||||
<!-- Spending Breakdown -->
|
||||
<Border Grid.Column="0" Classes="card" Margin="0,0,16,0">
|
||||
<StackPanel Spacing="20">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
@@ -117,22 +115,14 @@
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="March 2026" />
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Column="1" SelectedIndex="0" Background="{DynamicResource BgHover}"
|
||||
Foreground="{DynamicResource TextSecondary}"
|
||||
BorderBrush="{DynamicResource BorderAccent}" CornerRadius="{StaticResource RadiusIcon}" Padding="10,6">
|
||||
<ComboBoxItem Content="This Month" /> <ComboBoxItem Content="Last Month" />
|
||||
<ComboBoxItem Content="This Quarter" />
|
||||
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding ChartTimePeriods}"
|
||||
SelectedItem="{Binding SelectedChartTimePeriod}" Background="{DynamicResource BgHover}"
|
||||
Foreground="{DynamicResource TextSecondary}" BorderBrush="{DynamicResource BorderAccent}"
|
||||
CornerRadius="{StaticResource RadiusIcon}" Padding="10,6">
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<lvc:CartesianChart SeriesSource="{Binding ChartData}" Height="250" Background="{DynamicResource BgSurface}"
|
||||
LegendPosition="Hidden" TooltipPosition="Hidden">
|
||||
<lvc:CartesianChart.SeriesTemplate>
|
||||
<DataTemplate x:DataType="vm:ChartData">
|
||||
<lvc:XamlColumnSeries SeriesName="{Binding Name}" Fill="{Binding Fill}" Values="{Binding Values}" Padding="4"
|
||||
MaxBarWidth="9999999" YToolTipLabelFormatter="{Binding ToolTipFormatter}" />
|
||||
</DataTemplate>
|
||||
</lvc:CartesianChart.SeriesTemplate>
|
||||
<lvc:CartesianChart Series="{Binding SpendingByCategoryChartSeries}" Height="250" Background="{DynamicResource BgSurface}"
|
||||
LegendPosition="Hidden" TooltipPosition="Hidden" ZoomMode="None" Name="chart">
|
||||
<lvc:CartesianChart.XAxes>
|
||||
<lvc:XamlAxis IsVisible="False" />
|
||||
</lvc:CartesianChart.XAxes>
|
||||
@@ -140,179 +130,93 @@
|
||||
<lvc:XamlLogarithmicAxis LogBase="10" IsVisible="False" MinLimit="1" />
|
||||
</lvc:CartesianChart.YAxes>
|
||||
</lvc:CartesianChart>
|
||||
<ItemsControl ItemsSource="{Binding ChartData}" Margin="0 -10 ">
|
||||
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 ">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ChartData">
|
||||
<DataTemplate x:DataType="model:ColumnChartData">
|
||||
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Foreground="{DynamicResource TextDisabled}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Border HorizontalAlignment="Stretch" Height="1" Background="{DynamicResource BorderSubtle}" Margin="5 0" />
|
||||
<ItemsControl ItemsSource="{Binding ChartData}" Margin="0 -10 0 0 ">
|
||||
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 0 0 ">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ChartData">
|
||||
<DataTemplate x:DataType="model:ColumnChartData">
|
||||
<TextBlock Text="{Binding Values, Converter={StaticResource FirstValueConverter},StringFormat='$0,00'}"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{Binding Fill, Converter={StaticResource SkPaintToBrushConverter}}" FontWeight="SemiBold" />
|
||||
HorizontalAlignment="Center" Foreground="{Binding Fill, Converter={StaticResource SkPaintToBrushConverter}}"
|
||||
FontWeight="SemiBold" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<!-- <Grid RowDefinitions="*,Auto" Height="200"> -->
|
||||
<!-- <Grid Grid.Row="0" ColumnDefinitions="*,*,*,*,*,*" VerticalAlignment="Stretch"> -->
|
||||
<!-- <Grid Grid.Column="0" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="140" Background="{DynamicResource AccentBlue}" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Housing" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- <Grid Grid.Column="1" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="80" Background="{DynamicResource AccentGreen}" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Food" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- <Grid Grid.Column="2" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="55" Background="{DynamicResource AccentYellow}" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Transport" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- <Grid Grid.Column="3" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="45" Background="#FF7E5E" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Health" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- <Grid Grid.Column="4" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="35" Background="{DynamicResource AccentPurple}" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Leisure" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- <Grid Grid.Column="5" RowDefinitions="*,Auto" Margin="6,0"> -->
|
||||
<!-- <Border Grid.Row="0" VerticalAlignment="Bottom" CornerRadius="4,4,0,0" -->
|
||||
<!-- Height="25" Background="{DynamicResource AccentPink}" /> -->
|
||||
<!-- <TextBlock Grid.Row="1" Text="Other" Classes="muted" -->
|
||||
<!-- HorizontalAlignment="Center" Margin="0,6,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- ~1~ baseline @1@ -->
|
||||
<!-- <Border Grid.Row="1" Height="1" Background="{DynamicResource BorderSubtle}" -->
|
||||
<!-- Margin="0,8,0,0" /> -->
|
||||
<!-- </Grid> -->
|
||||
<!-- ~1~ Legend amounts @1@ -->
|
||||
<!-- <Grid ColumnDefinitions="*,*,*,*,*,*"> -->
|
||||
<!-- <TextBlock Grid.Column="0" Text="$1,200" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="{DynamicResource AccentBlue}" HorizontalAlignment="Center" /> -->
|
||||
<!-- <TextBlock Grid.Column="1" Text="$680" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="{DynamicResource AccentGreen}" HorizontalAlignment="Center" /> -->
|
||||
<!-- <TextBlock Grid.Column="2" Text="$420" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="{DynamicResource AccentYellow}" HorizontalAlignment="Center" /> -->
|
||||
<!-- <TextBlock Grid.Column="3" Text="$340" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="#FF7E5E" HorizontalAlignment="Center" /> -->
|
||||
<!-- <TextBlock Grid.Column="4" Text="$290" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="{DynamicResource AccentPurple}" HorizontalAlignment="Center" /> -->
|
||||
<!-- <TextBlock Grid.Column="5" Text="$210" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold" -->
|
||||
<!-- Foreground="{DynamicResource AccentPink}" HorizontalAlignment="Center" /> -->
|
||||
<!-- </Grid> -->
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Budget Tracker -->
|
||||
<Border Grid.Column="1" Classes="card">
|
||||
<ScrollViewer>
|
||||
<ScrollViewer Padding="12 0" Margin="-12 0">
|
||||
<StackPanel Spacing="20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Budget Tracker" FontSize="{StaticResource FontSizeSectionHeading}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="Monthly limits" />
|
||||
</StackPanel>
|
||||
<!-- Budget Item 1 -->
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/house.svg" Height="13" Width="13"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="Housing" FontSize="{StaticResource FontSizeBody}" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="$1,200 / $1,500" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="green" Minimum="0" Maximum="100" Value="80" />
|
||||
</StackPanel>
|
||||
<Separator />
|
||||
<!-- Budget Item 2 -->
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/hamburger.svg" Height="13" Width="13" />
|
||||
<TextBlock Text="Food" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="$680 / $700" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="yellow" Minimum="0" Maximum="100" Value="97" />
|
||||
</StackPanel>
|
||||
<Separator />
|
||||
<!-- Budget Item 3 -->
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/car.svg" Height="13" Width="13" />
|
||||
<TextBlock Text="Transport" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="$420 / $400" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="red" Minimum="0" Maximum="100" Value="105" />
|
||||
</StackPanel>
|
||||
<Separator />
|
||||
<!-- Budget Item 4 -->
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/heart-pulse.svg" Height="13" Width="13" />
|
||||
<TextBlock Text="Health" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="$340 / $500" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="green" Minimum="0" Maximum="100" Value="68" />
|
||||
</StackPanel>
|
||||
<Separator />
|
||||
<!-- Budget Item 5 -->
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="../Assets/Icons/gamepad-2.svg" Height="13" Width="13" />
|
||||
<TextBlock Text="Entertainment" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="$290 / $300" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</Grid>
|
||||
<ProgressBar Classes="yellow" Minimum="0" Maximum="100" Value="96" />
|
||||
</StackPanel>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding BudgetsTrackerData}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="20" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Budget">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
|
||||
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}" Height="13" Width="13"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding Category.Name}" FontSize="{StaticResource FontSizeBody}"
|
||||
VerticalAlignment="Center" Foreground="{DynamicResource TextSecondary}" />
|
||||
</StackPanel>
|
||||
<Panel Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal" IsVisible="{Binding !IsOverBudget}">
|
||||
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsOverBudget}">
|
||||
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
|
||||
Foreground="{DynamicResource AccentRed}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
<ProgressBar Classes.green="{Binding IsOnTrack}" Classes.yellow="{Binding IsWarning}"
|
||||
Classes.red="{Binding IsOverBudget}" Minimum="0" Value="{Binding Spent}"
|
||||
Maximum="{Binding LimitAmount}" />
|
||||
<Separator Margin="-8 4" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- ── Bottom Row: Recent Transactions + Accounts ─ -->
|
||||
<!-- Bottom Row: Recent Transactions + Accounts -->
|
||||
|
||||
<Grid ColumnDefinitions="*,300" MaxHeight="500">
|
||||
<!-- Recent Transactions -->
|
||||
<Border Grid.Column="0" Classes="card" Margin="0,0,16,0">
|
||||
@@ -321,181 +225,110 @@
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Recent Transactions" FontSize="{StaticResource FontSizeSectionHeading}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBlock Classes="muted" Text="Last 7 days" />
|
||||
<TextBlock Classes="muted" Text="Last 5 transactions" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Background="Transparent"
|
||||
Foreground="{DynamicResource AccentBlue}" BorderThickness="0" FontSize="{StaticResource FontSizeBody}"
|
||||
Cursor="Hand" Content="View all →" VerticalAlignment="Center" Command="{Binding ViewAllTransactionsCommand}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Transaction Row Template -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,4,0,0">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="{StaticResource RadiusControl}"
|
||||
Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/shopping-cart.svg" Height="18" Width="18"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Grocery Shopping" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Food · Mar 5" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="- $127.40" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgPurple}" CornerRadius="{StaticResource RadiusControl}"
|
||||
Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/banknote-arrow-up.svg" Height="18" Width="18"
|
||||
Css="{DynamicResource SvgPurple}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Salary Deposit" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Income · Mar 4" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="+ $3,425.00" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentGreen}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgOrange}" CornerRadius="{StaticResource RadiusControl}"
|
||||
Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/zap.svg" Height="18" Width="18"
|
||||
Css="{DynamicResource SvgOrange}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Electricity Bill" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Utilities · Mar 3" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="- $88.00" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgPink}" CornerRadius="{StaticResource RadiusControl}"
|
||||
Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/pill.svg" Height="18" Width="18"
|
||||
Css="{DynamicResource SvgPink}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Pharmacy" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Health · Mar 2" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="- $34.50" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="{StaticResource RadiusControl}"
|
||||
Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Svg Path="../Assets/Icons/hand-coins.svg" Height="18" Width="18"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Freelance Payment" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Income · Mar 1" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="+ $850.00" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentGreen}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Background="Transparent" Foreground="{DynamicResource AccentBlue}" BorderThickness="0"
|
||||
FontSize="{StaticResource FontSizeBody}" Cursor="Hand" Content="View all →" VerticalAlignment="Center"
|
||||
Command="{Binding ViewAllTransactionsCommand}" />
|
||||
</Grid>
|
||||
<ItemsControl ItemsSource="{Binding RecentTransactions}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="18" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Transaction">
|
||||
<StackPanel Spacing="4">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,4,0,0">
|
||||
<Border Grid.Column="0" CornerRadius="{StaticResource RadiusControl}" Width="42" Height="42" Margin="0,0,14,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Category.Icon,Converter={StaticResource SvgPathFromName}}" Height="18" Width="18"
|
||||
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Description}" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Category.Name}" Classes="muted" /> <TextBlock Text=" · " Classes="muted" />
|
||||
<TextBlock
|
||||
Text="{Binding Date,Converter={StaticResource DateFormatConverter},ConverterParameter='MMM d'}"
|
||||
Classes="muted" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
|
||||
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource AmountSignConverter}">
|
||||
<Binding Path="Amount" /> <Binding Path="Type" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<Separator />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Accounts Summary -->
|
||||
<Border Grid.Column="1" Classes="card">
|
||||
<StackPanel Spacing="18">
|
||||
<StackPanel>
|
||||
<Grid RowDefinitions="Auto,*,Auto" RowSpacing="18">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock Text="Accounts" FontSize="{StaticResource FontSizeSectionHeading}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
|
||||
<TextBlock Classes="muted" Text="4 linked accounts" />
|
||||
<TextBlock Classes="muted" Text="{Binding AccountsSubtitle}" />
|
||||
</StackPanel>
|
||||
<!-- Account 1 -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="{StaticResource RadiusInset}" Padding="14,12"
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgBlue}" CornerRadius="{StaticResource RadiusIcon}"
|
||||
Width="36" Height="36" Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/landmark.svg" Height="16" Width="16"
|
||||
Css="{DynamicResource SvgBlue}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Main Checking" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="•••• 4821" FontSize="{StaticResource FontSizeLabel}" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="$12,450" FontSize="{StaticResource FontSizeBody}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" VerticalAlignment="Center" />
|
||||
<ItemsControl ItemsSource="{Binding AccountsSummaryData}" Grid.Row="1">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="18" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:Account">
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="{StaticResource RadiusInset}" Padding="14,12"
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" CornerRadius="{StaticResource RadiusIcon}" Width="36" Height="36" Margin="0,0,12,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||
Opacity="0.15" />
|
||||
</Border.Background>
|
||||
<Svg Path="{Binding Icon, Converter={StaticResource SvgPathFromName}}" Height="16" Width="16"
|
||||
Css="{Binding Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="{Binding Mask, Converter={StaticResource MaskToStringConverter}}"
|
||||
FontSize="{StaticResource FontSizeLabel}" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="{Binding CurrentBalance, StringFormat='$0'}"
|
||||
FontSize="{StaticResource FontSizeBody}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel Spacing="18" Grid.Row="2">
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Total Balance" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalNetworth, StringFormat=$0}"
|
||||
FontSize="{StaticResource FontSizeSectionHeading}" FontWeight="Bold" Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Account 2 -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="{StaticResource RadiusInset}" Padding="14,12"
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgGreen}" CornerRadius="{StaticResource RadiusIcon}"
|
||||
Width="36" Height="36" Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/piggy-bank.svg" Height="16" Width="16"
|
||||
Css="{DynamicResource SvgGreen}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Savings Account" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="•••• 2034" FontSize="{StaticResource FontSizeLabel}" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="$38,700" FontSize="{StaticResource FontSizeBody}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentGreen}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Account 3 -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="{StaticResource RadiusInset}" Padding="14,12"
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgOrange}"
|
||||
CornerRadius="{StaticResource RadiusIcon}" Width="36" Height="36" Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/credit-card.svg" Height="16" Width="16"
|
||||
Css="{DynamicResource SvgOrange}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Credit Card" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="•••• 9170" FontSize="{StaticResource FontSizeLabel}" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="-$1,830" FontSize="{StaticResource FontSizeBody}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentRed}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Account 4 -->
|
||||
<Border Background="{DynamicResource BgBase}" CornerRadius="{StaticResource RadiusInset}" Padding="14,12"
|
||||
BorderBrush="{DynamicResource BorderSubtle}" BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Background="{DynamicResource IconBgPurple}"
|
||||
CornerRadius="{StaticResource RadiusIcon}" Width="36" Height="36" Margin="0,0,12,0">
|
||||
<Svg Path="../Assets/Icons/chart-column-big.svg" Height="16" Width="16"
|
||||
Css="{DynamicResource SvgPurple}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="Investment" FontSize="{StaticResource FontSizeMeta}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextSecondary}" />
|
||||
<TextBlock Text="Portfolio" FontSize="{StaticResource FontSizeLabel}" Classes="muted" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="$35,000" FontSize="{StaticResource FontSizeBody}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource AccentBlue}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Total -->
|
||||
<Separator />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="Total Balance" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextMuted}" />
|
||||
|
||||
<TextBlock Grid.Column="1" Text="$84,320" FontSize="{StaticResource FontSizeSectionHeading}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
@@ -9,5 +13,17 @@ public partial class DashboardView : UserControl
|
||||
public DashboardView()
|
||||
{
|
||||
InitializeComponent();
|
||||
chart.AddHandler(PointerWheelChangedEvent, OnChartScroll, RoutingStrategies.Tunnel);
|
||||
}
|
||||
|
||||
private void OnChartScroll(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
var offset = mainScrollviewer.Offset;
|
||||
mainScrollviewer.Offset = new Vector(
|
||||
offset.X,
|
||||
offset.Y - e.Delta.Y * mainScrollviewer.SmallChange.Height * 3
|
||||
);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
14
Clario/Views/LoadingView.axaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avaloniaProgressRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
|
||||
Background="{DynamicResource BgBase}"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Clario.Views.LoadingView">
|
||||
<Panel>
|
||||
<avaloniaProgressRing:ProgressRing Width="100" Height="100" IsActive="True"
|
||||
Foreground="{DynamicResource AccentBlue}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Panel>
|
||||
</UserControl>
|
||||
13
Clario/Views/LoadingView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Clario.Views;
|
||||
|
||||
public partial class LoadingView : UserControl
|
||||
{
|
||||
public LoadingView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||