Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9c155b272 | |||
| fe0f1d98ef | |||
| 3a52f5c19f | |||
| 5a6d96ca97 | |||
| 0c52789cf6 | |||
| b50ee1a251 | |||
| 50ae20abd1 | |||
|
|
79e1d52391 |
42
.github/workflows/build-linux.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Build Linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore Clario/Clario.csproj
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build Clario/Clario.csproj --configuration Release --no-restore
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: dotnet publish Clario/Clario.csproj \
|
||||||
|
--configuration Release \
|
||||||
|
--runtime linux-x64 \
|
||||||
|
--self-contained true \
|
||||||
|
--output ./publish/linux \
|
||||||
|
-p:PublishSingleFile=true \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Clario-linux-x64
|
||||||
|
path: ./publish/linux
|
||||||
|
retention-days: 7
|
||||||
456
.gitignore
vendored
@@ -1,454 +1,6 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
bin/
|
||||||
## files generated by popular Visual Studio add-ons.
|
obj/
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# Tye
|
|
||||||
.tye/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
##
|
|
||||||
## Visual studio for Mac
|
|
||||||
##
|
|
||||||
|
|
||||||
|
|
||||||
# globs
|
|
||||||
Makefile.in
|
|
||||||
*.userprefs
|
|
||||||
*.usertasks
|
|
||||||
config.make
|
|
||||||
config.status
|
|
||||||
aclocal.m4
|
|
||||||
install-sh
|
|
||||||
autom4te.cache/
|
|
||||||
*.tar.gz
|
|
||||||
tarballs/
|
|
||||||
test-results/
|
|
||||||
|
|
||||||
# Mac bundle stuff
|
|
||||||
*.dmg
|
|
||||||
*.app
|
|
||||||
|
|
||||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
|
||||||
# General
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
|
||||||
# Windows thumbnail cache files
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
ehthumbs_vista.db
|
|
||||||
|
|
||||||
# Dump file
|
|
||||||
*.stackdump
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
[Dd]esktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
.idea/
|
||||||
*.sln.iml
|
*.user
|
||||||
|
*.suo
|
||||||
##
|
|
||||||
## Visual Studio Code
|
|
||||||
##
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
@@ -13,16 +13,13 @@
|
|||||||
<RootNamespace>Clario.Android</RootNamespace>
|
<RootNamespace>Clario.Android</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<AndroidResource Include="Icon.png">
|
|
||||||
<Link>Resources\drawable\Icon.png</Link>
|
|
||||||
</AndroidResource>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Android"/>
|
<PackageReference Include="Avalonia.Android"/>
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||||
|
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||||
|
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||||
<PackageReference Include="Supabase" />
|
<PackageReference Include="Supabase" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen"/>
|
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -30,4 +27,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Clario\Clario.csproj" />
|
<ProjectReference Include="..\Clario\Clario.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Resources\drawable-night-v31\" />
|
||||||
|
<Folder Include="Resources\drawable-v31\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</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"/>
|
<color android:color="@color/splash_background"/>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item android:drawable="@drawable/icon"
|
<item android:drawable="@drawable/Icon"
|
||||||
android:width="120dp"
|
android:width="120dp"
|
||||||
android:height="120dp"
|
android:height="120dp"
|
||||||
android:gravity="center" />
|
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:windowBackground">@null</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowSplashScreenBackground">@color/splash_background</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="android:windowSplashScreenAnimationDuration">1000</item>
|
||||||
<item name="postSplashScreenTheme">@style/MyTheme.Main</item>
|
<item name="postSplashScreenTheme">@style/MyTheme.Main</item>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="splash_background">#FFFFFF</color>
|
<color name="splash_background">#0B0D12</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
BIN
Clario.Android/play_store_512.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -9,7 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Browser"/>
|
<PackageReference Include="Avalonia.Browser"/>
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||||
|
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||||
|
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||||
<PackageReference Include="Supabase" />
|
<PackageReference Include="Supabase" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<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>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -19,7 +17,10 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||||
|
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||||
|
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||||
<PackageReference Include="Supabase" />
|
<PackageReference Include="Supabase" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Clario.Services;
|
|
||||||
|
|
||||||
namespace Clario.Desktop;
|
namespace Clario.Desktop;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.iOS"/>
|
<PackageReference Include="Avalonia.iOS"/>
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||||
|
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||||
|
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||||
<PackageReference Include="Supabase" />
|
<PackageReference Include="Supabase" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Clario"
|
xmlns:local="using:Clario"
|
||||||
xmlns:converters="clr-namespace:Clario.Converters"
|
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"
|
x:Class="Clario.App"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Default">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
<Application.DataTemplates>
|
<Application.DataTemplates>
|
||||||
<local:ViewLocator />
|
<local:ViewLocator />
|
||||||
</Application.DataTemplates>
|
</Application.DataTemplates>
|
||||||
@@ -24,9 +26,12 @@
|
|||||||
<converters:DecimalSignConverter x:Key="DecimalSignConverter" />
|
<converters:DecimalSignConverter x:Key="DecimalSignConverter" />
|
||||||
<converters:PercentageConverter x:Key="PercentageConverter" />
|
<converters:PercentageConverter x:Key="PercentageConverter" />
|
||||||
<converters:DecimalColorConverter x:Key="DecimalColorConverter" />
|
<converters:DecimalColorConverter x:Key="DecimalColorConverter" />
|
||||||
|
<converters:BoolToColorConverter x:Key="BoolToColorConverter" />
|
||||||
|
<converters:BoolToCssConverter x:Key="BoolToCssConverter" />
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
<StyleInclude Source="../Theme/AppTheme.axaml" />
|
<StyleInclude Source="../Theme/AppTheme.axaml" />
|
||||||
|
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@@ -2,11 +2,10 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Data.Core;
|
|
||||||
using Avalonia.Data.Core.Plugins;
|
using Avalonia.Data.Core.Plugins;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Styling;
|
||||||
using Clario.Data;
|
using Clario.Data;
|
||||||
using Clario.Services;
|
using Clario.Services;
|
||||||
using Clario.ViewModels;
|
using Clario.ViewModels;
|
||||||
@@ -16,15 +15,42 @@ namespace Clario;
|
|||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
public static bool IsMobile { get; private set; }
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
RequestedThemeVariant = ThemeVariant.Dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnFrameworkInitializationCompleted()
|
public override async void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
base.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");
|
var culture = new CultureInfo("en-US");
|
||||||
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||||
@@ -36,7 +62,6 @@ public partial class App : Application
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
/* session invalid or expired */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = SupabaseService.Client.Auth.CurrentUser;
|
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
|
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
||||||
DisableAvaloniaDataAnnotationValidation();
|
DisableAvaloniaDataAnnotationValidation();
|
||||||
|
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow!.DataContext = user is not null ? new MainViewModel() : new AuthViewModel();
|
||||||
{
|
|
||||||
DataContext = user is not null ? new MainViewModel() : new AuthViewModel()
|
|
||||||
};
|
|
||||||
desktop.MainWindow.Show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||||
{
|
{
|
||||||
singleViewPlatform.MainView = new MainView
|
Console.WriteLine("ANDROID PATH HIT");
|
||||||
{
|
singleViewPlatform.MainView!.DataContext = user is not null ? new MainViewModel() : new AuthViewModel();
|
||||||
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/chart-bar.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-chart-bar-icon lucide-chart-bar"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16h8"/><path d="M7 11h12"/><path d="M7 6h3"/></svg>
|
||||||
|
After Width: | Height: | Size: 334 B |
1
Clario/Assets/Icons/chart-column.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-chart-column-icon lucide-chart-column"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg>
|
||||||
|
After Width: | Height: | Size: 343 B |
1
Clario/Assets/Icons/chevron-down.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-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
|
||||||
|
After Width: | Height: | Size: 271 B |
1
Clario/Assets/Icons/receipt.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-receipt-icon lucide-receipt"><path d="M12 17V7"/><path d="M16 8h-6a2 2 0 0 0 0 4h4a2 2 0 0 1 0 4H8"/><path d="M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"/></svg>
|
||||||
|
After Width: | Height: | Size: 758 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 |
BIN
Clario/Assets/logo-no-bg.ico
Normal file
|
After Width: | Height: | Size: 171 KiB |
@@ -1,28 +1,47 @@
|
|||||||
<svg width="800" height="400" viewBox="0 0 800 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="800" height="400" viewBox="0 0 800 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M211.696 66.3202C212.178 60.8183 208.105 55.9315 202.583 55.833C175.756 55.3546 149.254 62.3834 126.112 76.2222C102.97 90.061 84.238 110.082 71.9651 133.942C69.4389 138.853 71.817 144.754 76.8916 146.933L105.77 159.335C110.844 161.514 116.679 159.133 119.393 154.323C127.265 140.368 138.658 128.642 152.507 120.361C166.356 112.079 182.077 107.591 198.096 107.259C203.617 107.144 208.476 103.131 208.957 97.6291L211.696 66.3202Z" fill="url(#paint0_linear_104_124)"/>
|
<path d="M331.8 292V146.8H362.4V292H331.8Z" fill="url(#paint0_linear_104_124)"/>
|
||||||
<path d="M76.8916 146.933C71.817 144.754 65.9008 147.092 64.0787 152.306C53.1091 183.693 53.3093 218.027 64.8376 249.432C76.3659 280.837 98.4269 307.147 127.098 323.983C131.861 326.779 137.885 324.735 140.344 319.79L154.34 291.649C156.799 286.704 154.748 280.746 150.097 277.768C133.148 266.915 120.114 250.773 113.116 231.71C106.118 212.647 105.613 191.906 111.515 172.665C113.135 167.385 110.844 161.514 105.77 159.335L76.8916 146.933Z" fill="url(#paint1_linear_104_124)"/>
|
<path d="M424.548 294C415.615 294 407.615 291.8 400.548 287.4C393.615 283 388.082 277 383.948 269.4C379.948 261.8 377.948 253.133 377.948 243.4C377.948 233.667 379.948 225 383.948 217.4C388.082 209.8 393.615 203.8 400.548 199.4C407.615 195 415.615 192.8 424.548 192.8C431.082 192.8 436.948 194.067 442.148 196.6C447.482 199.133 451.815 202.667 455.148 207.2C458.482 211.6 460.348 216.667 460.748 222.4V264.4C460.348 270.133 458.482 275.267 455.148 279.8C451.948 284.2 447.682 287.667 442.348 290.2C437.015 292.733 431.082 294 424.548 294ZM430.748 266.4C437.282 266.4 442.548 264.267 446.548 260C450.548 255.6 452.548 250.067 452.548 243.4C452.548 238.867 451.615 234.867 449.748 231.4C448.015 227.933 445.482 225.267 442.148 223.4C438.948 221.4 435.215 220.4 430.948 220.4C426.682 220.4 422.882 221.4 419.548 223.4C416.348 225.267 413.748 227.933 411.748 231.4C409.882 234.867 408.948 238.867 408.948 243.4C408.948 247.8 409.882 251.733 411.748 255.2C413.615 258.667 416.215 261.4 419.548 263.4C422.882 265.4 426.615 266.4 430.748 266.4ZM451.348 292V265.8L455.948 242.2L451.348 218.6V194.8H481.348V292H451.348Z" fill="url(#paint1_linear_104_124)"/>
|
||||||
<path d="M140.344 319.79C137.885 324.735 139.889 330.772 144.992 332.882C172.819 344.389 203.645 346.914 233.115 339.955C262.584 332.995 289.023 316.946 308.762 294.206C312.383 290.035 311.474 283.739 307.062 280.417L281.956 261.511C277.544 258.189 271.311 259.116 267.532 263.144C255.135 276.36 239.072 285.704 221.294 289.903C203.516 294.102 184.97 292.931 167.97 286.657C162.789 284.745 156.799 286.704 154.34 291.649L140.344 319.79Z" fill="url(#paint2_linear_104_124)"/>
|
<path d="M502.894 292V194.8H533.494V292H502.894ZM533.494 238.6L520.694 228.6C523.227 217.267 527.494 208.467 533.494 202.2C539.494 195.933 547.827 192.8 558.494 192.8C563.16 192.8 567.227 193.533 570.694 195C574.294 196.333 577.427 198.467 580.094 201.4L561.894 224.4C560.56 222.933 558.894 221.8 556.894 221C554.894 220.2 552.627 219.8 550.094 219.8C545.027 219.8 540.96 221.4 537.894 224.6C534.96 227.667 533.494 232.333 533.494 238.6Z" fill="url(#paint2_linear_104_124)"/>
|
||||||
<path d="M307.676 120.025C312.113 116.736 313.07 110.448 309.482 106.25C298.448 93.3399 285.199 82.4632 270.333 74.1449C255.467 65.8267 239.266 60.2252 222.492 57.5748C217.037 56.7128 212.178 60.8183 211.696 66.3202L208.957 97.6291C208.476 103.131 212.563 107.927 217.981 108.998C227.511 110.884 236.706 114.261 245.22 119.025C253.734 123.789 261.423 129.859 268.015 136.994C271.763 141.051 277.988 142.026 282.425 138.738L307.676 120.025Z" fill="url(#paint3_linear_104_124)"/>
|
<path d="M588.245 292V194.8H618.845V292H588.245ZM603.645 181.4C598.845 181.4 594.845 179.8 591.645 176.6C588.579 173.267 587.045 169.267 587.045 164.6C587.045 159.8 588.579 155.8 591.645 152.6C594.845 149.4 598.845 147.8 603.645 147.8C608.445 147.8 612.379 149.4 615.445 152.6C618.512 155.8 620.045 159.8 620.045 164.6C620.045 169.267 618.512 173.267 615.445 176.6C612.379 179.8 608.445 181.4 603.645 181.4Z" fill="url(#paint3_linear_104_124)"/>
|
||||||
<path d="M105.77 159.335L76.8916 146.933M105.77 159.335C110.844 161.514 116.679 159.133 119.393 154.323C127.265 140.368 138.658 128.642 152.507 120.361C166.356 112.079 182.077 107.591 198.096 107.259C203.617 107.144 208.476 103.131 208.957 97.6291M105.77 159.335C110.844 161.514 113.135 167.385 111.515 172.665C105.613 191.906 106.118 212.647 113.116 231.71C120.114 250.773 133.148 266.915 150.097 277.768C154.748 280.746 156.799 286.704 154.34 291.649M76.8916 146.933C71.817 144.754 69.4389 138.853 71.9651 133.942C84.238 110.082 102.97 90.061 126.112 76.2222C149.254 62.3834 175.756 55.3546 202.583 55.833C208.105 55.9315 212.178 60.8183 211.696 66.3202M76.8916 146.933C71.817 144.754 65.9008 147.092 64.0787 152.306C53.1091 183.693 53.3093 218.027 64.8376 249.432C76.3659 280.837 98.4269 307.147 127.098 323.983C131.861 326.779 137.885 324.735 140.344 319.79M208.957 97.6291L211.696 66.3202M208.957 97.6291C208.476 103.131 212.563 107.927 217.981 108.998C227.511 110.884 236.706 114.261 245.22 119.025C253.734 123.789 261.423 129.859 268.015 136.994C271.763 141.051 277.988 142.026 282.425 138.738L307.676 120.025C312.113 116.736 313.07 110.448 309.482 106.25C298.448 93.3399 285.199 82.4632 270.333 74.1449C255.467 65.8267 239.266 60.2252 222.492 57.5748C217.037 56.7128 212.178 60.8183 211.696 66.3202M154.34 291.649L140.344 319.79M154.34 291.649C156.799 286.704 162.789 284.745 167.97 286.657C184.97 292.931 203.516 294.102 221.294 289.903C239.072 285.704 255.135 276.36 267.532 263.144C271.311 259.116 277.544 258.189 281.956 261.511L307.062 280.417C311.474 283.739 312.383 290.035 308.762 294.206C289.023 316.946 262.584 332.995 233.115 339.955C203.645 346.914 172.819 344.389 144.992 332.882C139.889 330.772 137.885 324.735 140.344 319.79" stroke="#13161E" stroke-width="5"/>
|
<path d="M687.194 294.2C677.194 294.2 668.127 292 659.994 287.6C651.994 283.067 645.66 276.933 640.994 269.2C636.327 261.467 633.994 252.8 633.994 243.2C633.994 233.6 636.327 225 640.994 217.4C645.66 209.8 651.994 203.8 659.994 199.4C667.994 194.867 677.06 192.6 687.194 192.6C697.327 192.6 706.394 194.8 714.394 199.2C722.394 203.6 728.727 209.667 733.394 217.4C738.06 225 740.394 233.6 740.394 243.2C740.394 252.8 738.06 261.467 733.394 269.2C728.727 276.933 722.394 283.067 714.394 287.6C706.394 292 697.327 294.2 687.194 294.2ZM687.194 266.4C691.594 266.4 695.46 265.467 698.794 263.6C702.127 261.6 704.66 258.867 706.394 255.4C708.26 251.8 709.194 247.733 709.194 243.2C709.194 238.667 708.26 234.733 706.394 231.4C704.527 227.933 701.927 225.267 698.594 223.4C695.394 221.4 691.594 220.4 687.194 220.4C682.927 220.4 679.127 221.4 675.794 223.4C672.46 225.267 669.86 227.933 667.994 231.4C666.127 234.867 665.194 238.867 665.194 243.4C665.194 247.8 666.127 251.8 667.994 255.4C669.86 258.867 672.46 261.6 675.794 263.6C679.127 265.467 682.927 266.4 687.194 266.4Z" fill="url(#paint4_linear_104_124)"/>
|
||||||
<path d="M331.8 292V146.8H362.4V292H331.8ZM424.548 294C415.615 294 407.615 291.8 400.548 287.4C393.615 283 388.082 277 383.948 269.4C379.948 261.8 377.948 253.133 377.948 243.4C377.948 233.667 379.948 225 383.948 217.4C388.082 209.8 393.615 203.8 400.548 199.4C407.615 195 415.615 192.8 424.548 192.8C431.082 192.8 436.948 194.067 442.148 196.6C447.482 199.133 451.815 202.667 455.148 207.2C458.482 211.6 460.348 216.667 460.748 222.4V264.4C460.348 270.133 458.482 275.267 455.148 279.8C451.948 284.2 447.682 287.667 442.348 290.2C437.015 292.733 431.082 294 424.548 294ZM430.748 266.4C437.282 266.4 442.548 264.267 446.548 260C450.548 255.6 452.548 250.067 452.548 243.4C452.548 238.867 451.615 234.867 449.748 231.4C448.015 227.933 445.482 225.267 442.148 223.4C438.948 221.4 435.215 220.4 430.948 220.4C426.682 220.4 422.882 221.4 419.548 223.4C416.348 225.267 413.748 227.933 411.748 231.4C409.882 234.867 408.948 238.867 408.948 243.4C408.948 247.8 409.882 251.733 411.748 255.2C413.615 258.667 416.215 261.4 419.548 263.4C422.882 265.4 426.615 266.4 430.748 266.4ZM451.348 292V265.8L455.948 242.2L451.348 218.6V194.8H481.348V292H451.348ZM502.894 292V194.8H533.494V292H502.894ZM533.494 238.6L520.694 228.6C523.227 217.267 527.494 208.467 533.494 202.2C539.494 195.933 547.827 192.8 558.494 192.8C563.16 192.8 567.227 193.533 570.694 195C574.294 196.333 577.427 198.467 580.094 201.4L561.894 224.4C560.56 222.933 558.894 221.8 556.894 221C554.894 220.2 552.627 219.8 550.094 219.8C545.027 219.8 540.96 221.4 537.894 224.6C534.96 227.667 533.494 232.333 533.494 238.6ZM588.245 292V194.8H618.845V292H588.245ZM603.645 181.4C598.845 181.4 594.845 179.8 591.645 176.6C588.579 173.267 587.045 169.267 587.045 164.6C587.045 159.8 588.579 155.8 591.645 152.6C594.845 149.4 598.845 147.8 603.645 147.8C608.445 147.8 612.379 149.4 615.445 152.6C618.512 155.8 620.045 159.8 620.045 164.6C620.045 169.267 618.512 173.267 615.445 176.6C612.379 179.8 608.445 181.4 603.645 181.4ZM687.194 294.2C677.194 294.2 668.127 292 659.994 287.6C651.994 283.067 645.66 276.933 640.994 269.2C636.327 261.467 633.994 252.8 633.994 243.2C633.994 233.6 636.327 225 640.994 217.4C645.66 209.8 651.994 203.8 659.994 199.4C667.994 194.867 677.06 192.6 687.194 192.6C697.327 192.6 706.394 194.8 714.394 199.2C722.394 203.6 728.727 209.667 733.394 217.4C738.06 225 740.394 233.6 740.394 243.2C740.394 252.8 738.06 261.467 733.394 269.2C728.727 276.933 722.394 283.067 714.394 287.6C706.394 292 697.327 294.2 687.194 294.2ZM687.194 266.4C691.594 266.4 695.46 265.467 698.794 263.6C702.127 261.6 704.66 258.867 706.394 255.4C708.26 251.8 709.194 247.733 709.194 243.2C709.194 238.667 708.26 234.733 706.394 231.4C704.527 227.933 701.927 225.267 698.594 223.4C695.394 221.4 691.594 220.4 687.194 220.4C682.927 220.4 679.127 221.4 675.794 223.4C672.46 225.267 669.86 227.933 667.994 231.4C666.127 234.867 665.194 238.867 665.194 243.4C665.194 247.8 666.127 251.8 667.994 255.4C669.86 258.867 672.46 261.6 675.794 263.6C679.127 265.467 682.927 266.4 687.194 266.4Z" fill="url(#paint4_linear_104_124)"/>
|
<path d="M142.075 320.852C139.663 325.703 141.628 331.626 146.634 333.697C173.927 344.985 204.162 347.462 233.066 340.635C261.97 333.807 287.902 318.062 307.262 295.753C310.813 291.661 309.922 285.484 305.595 282.225L280.97 263.678C276.643 260.419 270.529 261.329 266.823 265.28C254.664 278.246 238.909 287.413 221.472 291.532C204.035 295.651 185.845 294.502 169.171 288.348C164.089 286.472 158.214 288.394 155.802 293.245L142.075 320.852Z" fill="url(#paint5_linear_104_124)"/>
|
||||||
|
<path d="M76.2161 149.27C71.2388 147.132 65.4361 149.426 63.6489 154.541C52.8897 185.333 53.0861 219.017 64.3932 249.827C75.7004 280.637 97.3382 306.447 125.46 322.964C130.131 325.708 136.039 323.702 138.451 318.85L152.179 291.244C154.591 286.392 152.579 280.547 148.017 277.625C131.393 266.978 118.609 251.142 111.746 232.44C104.882 213.739 104.387 193.39 110.175 174.515C111.764 169.335 109.518 163.575 104.54 161.437L76.2161 149.27Z" fill="url(#paint6_linear_104_124)"/>
|
||||||
|
<path d="M209.516 66.3108C209.988 60.9133 205.994 56.119 200.578 56.0225C174.265 55.5532 148.272 62.4487 125.574 76.0252C102.876 89.6016 84.5026 109.243 72.4651 132.651C69.9874 137.469 72.3198 143.258 77.2972 145.396L105.621 157.562C110.599 159.7 116.321 157.364 118.983 152.645C126.705 138.955 137.879 127.452 151.462 119.327C165.045 111.202 180.464 106.8 196.176 106.473C201.592 106.361 206.357 102.424 206.829 97.0263L209.516 66.3108Z" fill="url(#paint7_linear_104_124)"/>
|
||||||
|
<path d="M307.263 118.728C311.615 115.502 312.553 109.332 309.034 105.213C298.212 92.5486 285.217 81.878 270.636 73.7174C256.055 65.5569 240.165 60.0616 223.713 57.4614C218.362 56.6158 213.597 60.6435 213.124 66.041L210.438 96.7565C209.966 102.154 213.975 106.859 219.289 107.91C228.636 109.76 237.655 113.073 246.005 117.747C254.356 122.421 261.897 128.375 268.363 135.376C272.038 139.356 278.145 140.312 282.497 137.086L307.263 118.728Z" fill="url(#paint8_linear_104_124)"/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="paint0_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint0_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#7B9CFF"/>
|
<stop stop-color="#7B9CFF"/>
|
||||||
<stop offset="1" stop-color="#3B6AFF"/>
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint1_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint1_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#7B9CFF"/>
|
<stop stop-color="#7B9CFF"/>
|
||||||
<stop offset="1" stop-color="#3B6AFF"/>
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint2_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint2_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#7B9CFF"/>
|
<stop stop-color="#7B9CFF"/>
|
||||||
<stop offset="1" stop-color="#3B6AFF"/>
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint3_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint3_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#7B9CFF"/>
|
<stop stop-color="#7B9CFF"/>
|
||||||
<stop offset="1" stop-color="#3B6AFF"/>
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint4_linear_104_124" x1="745" y1="92" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint4_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#7B9CFF"/>
|
||||||
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint5_linear_104_124" x1="315.701" y1="70.2952" x2="259.223" y2="163.668" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#7B9CFF"/>
|
||||||
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint6_linear_104_124" x1="312.078" y1="68.2937" x2="255.6" y2="161.666" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#7B9CFF"/>
|
||||||
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint7_linear_104_124" x1="313.159" y1="64.4189" x2="256.681" y2="157.791" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#7B9CFF"/>
|
||||||
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint8_linear_104_124" x1="316.768" y1="64.1491" x2="260.29" y2="157.521" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#7B9CFF"/>
|
<stop stop-color="#7B9CFF"/>
|
||||||
<stop offset="1" stop-color="#3B6AFF"/>
|
<stop offset="1" stop-color="#3B6AFF"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.0 KiB |
65
Clario/Behaviors/NumericInputBehavior.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Xaml.Interactivity;
|
||||||
|
|
||||||
|
namespace Clario.Behaviors;
|
||||||
|
|
||||||
|
public class NumericInputBehavior : Behavior<TextBox>
|
||||||
|
{
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
base.OnAttached();
|
||||||
|
AssociatedObject!.AddHandler(TextBox.TextInputEvent, OnTextInput, RoutingStrategies.Tunnel);
|
||||||
|
AssociatedObject.TextChanged += OnTextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetaching()
|
||||||
|
{
|
||||||
|
base.OnDetaching();
|
||||||
|
AssociatedObject!.RemoveHandler(TextBox.TextInputEvent, OnTextInput);
|
||||||
|
AssociatedObject.TextChanged -= OnTextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextInput(object? sender, TextInputEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Text is null) return;
|
||||||
|
foreach (var c in e.Text)
|
||||||
|
{
|
||||||
|
if (!char.IsDigit(c) && c != '.')
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = (sender as TextBox)?.Text ?? "";
|
||||||
|
if (e.Text.Contains('.') && current.Contains('.'))
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not TextBox tb) return;
|
||||||
|
var text = tb.Text ?? "";
|
||||||
|
|
||||||
|
var clean = new string(text.Where(c => char.IsDigit(c) || c == '.').ToArray());
|
||||||
|
|
||||||
|
var dotIndex = clean.IndexOf('.');
|
||||||
|
if (dotIndex >= 0)
|
||||||
|
{
|
||||||
|
clean = clean[..(dotIndex + 1)] + clean[(dotIndex + 1)..].Replace(".", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clean != text)
|
||||||
|
{
|
||||||
|
var caret = tb.CaretIndex;
|
||||||
|
tb.Text = clean;
|
||||||
|
tb.CaretIndex = Math.Min(caret, clean.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,19 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm"/>
|
<PackageReference Include="CommunityToolkit.Mvvm"/>
|
||||||
|
<PackageReference Include="Deadpikle.AvaloniaProgressRing" />
|
||||||
|
<PackageReference Include="FluentAvalonia.ProgressRing" />
|
||||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
|
||||||
<PackageReference Include="Supabase" />
|
<PackageReference Include="Supabase" />
|
||||||
|
<PackageReference Include="Xaml.Behaviors.Interactions" />
|
||||||
|
<PackageReference Include="Xaml.Behaviors.Interactivity" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="MobileViews\MainAppMobile.axaml.cs">
|
||||||
|
<DependentUpon>MobileMainView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class AccountFromIdConverter : IValueConverter
|
|||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (value is not Guid) return null;
|
if (value is not Guid) return null;
|
||||||
var accounts = DataRepo.General.Accounts;
|
var accounts = DataRepo.General.FetchAccounts().Result;
|
||||||
if (accounts is null) return null;
|
if (accounts is null) return null;
|
||||||
return accounts.FirstOrDefault(x => x.Id == (Guid)value)?.Name;
|
return accounts.FirstOrDefault(x => x.Id == (Guid)value)?.Name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class AccountMaskToStringConverter : IValueConverter
|
|||||||
{
|
{
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (value is not string mask) return string.Empty;
|
if (value is not string mask || string.IsNullOrWhiteSpace(mask)) return string.Empty;
|
||||||
return $"•••• {mask}";
|
return $"•••• {mask}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ public class AmountSignConverter : IMultiValueConverter
|
|||||||
{
|
{
|
||||||
if (values.Any(x => x is null) || values.Count < 2) return 0;
|
if (values.Any(x => x is null) || values.Count < 2) return 0;
|
||||||
if (values[0] is decimal amount && values[1] is string type)
|
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;
|
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.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Media;
|
|
||||||
|
|
||||||
namespace Clario.Converters;
|
namespace Clario.Converters;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ public class DecimalSignConverter : IValueConverter
|
|||||||
{
|
{
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
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";
|
return "$0";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime.InteropServices.JavaScript;
|
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace Clario.Converters;
|
namespace Clario.Converters;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using LiveChartsCore.SkiaSharpView.Avalonia;
|
|
||||||
|
|
||||||
namespace Clario.Converters;
|
namespace Clario.Converters;
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public class PercentageConverter : IMultiValueConverter
|
|||||||
|
|
||||||
if (value[0] is decimal part && value[1] is decimal total && part > 0)
|
if (value[0] is decimal part && value[1] is decimal total && part > 0)
|
||||||
{
|
{
|
||||||
var percentage = Math.Round(part / total, 2);
|
var percentage = Math.Round(part / total, 3);
|
||||||
return percentage.ToString("0%");
|
return percentage.ToString("0.0%");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "0%";
|
return "0%";
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using Clario.Data;
|
|
||||||
|
|
||||||
namespace Clario.Converters;
|
namespace Clario.Converters;
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,85 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:Clario.CustomControls">
|
xmlns:local="clr-namespace:Clario.CustomControls">
|
||||||
<Design.PreviewWith>
|
|
||||||
<Border Padding="20">
|
|
||||||
<!-- Add Controls for Previewer Here -->
|
|
||||||
</Border>
|
|
||||||
</Design.PreviewWith>
|
|
||||||
|
|
||||||
<Style Selector="local|DateRangePicker">
|
<Style Selector="local|DateRangePicker">
|
||||||
<Setter Property="MinHeight" Value="15" />
|
<Setter Property="MinHeight" Value="15" />
|
||||||
<Setter Property="MinWidth" Value="50" />
|
<Setter Property="MinWidth" Value="50" />
|
||||||
<!-- <Setter Property="Background" Value="{TemplateBinding Background}" /> -->
|
<Setter Property="Background" Value="{DynamicResource BgBase}" />
|
||||||
<!-- <Setter Property="Foreground" Value="{TemplateBinding Foreground}" /> -->
|
<Setter Property="Foreground" Value="{DynamicResource TextSecondary}" />
|
||||||
<!-- <Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" /> -->
|
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||||
<!-- <Setter Property="CornerRadius" Value="{TemplateBinding CornerRadius}" /> -->
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||||
<!-- <Setter Property="BorderThickness" Value="1" /> -->
|
<Setter Property="Padding" Value="10,8 2 8" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
||||||
<!-- Button -->
|
<!-- Trigger button -->
|
||||||
<Button x:Name="PART_Button"
|
<Button x:Name="PART_Button"
|
||||||
Content="{TemplateBinding DisplayText}" HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Padding="{TemplateBinding Padding}"
|
|
||||||
FontSize="{TemplateBinding FontSize}"
|
|
||||||
FontWeight="{TemplateBinding FontWeight}"
|
|
||||||
Background="{TemplateBinding Background}"
|
Background="{TemplateBinding Background}"
|
||||||
Foreground="{TemplateBinding Foreground}"
|
Foreground="{TemplateBinding Foreground}"
|
||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
BorderThickness="1"
|
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"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<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 -->
|
||||||
<Popup x:Name="PART_Popup"
|
<Popup x:Name="PART_Popup"
|
||||||
PlacementTarget="{Binding #PART_Button}"
|
PlacementTarget="{Binding #PART_Button}"
|
||||||
|
Placement="Bottom"
|
||||||
IsLightDismissEnabled="True">
|
IsLightDismissEnabled="True">
|
||||||
|
<Border Background="{DynamicResource BgSurface}"
|
||||||
<Border Padding="8">
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Padding="4"
|
||||||
|
BoxShadow="0 8 32 0 #3C000000">
|
||||||
<Calendar x:Name="PART_Calendar"
|
<Calendar x:Name="PART_Calendar"
|
||||||
SelectionMode="{TemplateBinding SelectionMode}" />
|
SelectionMode="{TemplateBinding SelectionMode}"
|
||||||
|
Background="{DynamicResource BgSurface}"
|
||||||
|
BorderThickness="0" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</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>
|
</Styles>
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Calendar = Avalonia.Controls.Calendar;
|
||||||
|
|
||||||
namespace Clario.CustomControls;
|
namespace Clario.CustomControls;
|
||||||
|
|
||||||
@@ -19,7 +23,6 @@ public class DateRangePicker : TemplatedControl
|
|||||||
set => SetValue(SelectionModeProperty, value);
|
set => SetValue(SelectionModeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IList<DateTime>> SelectedDatesProperty =
|
public static readonly StyledProperty<IList<DateTime>> SelectedDatesProperty =
|
||||||
AvaloniaProperty.Register<DateRangePicker, IList<DateTime>>(
|
AvaloniaProperty.Register<DateRangePicker, IList<DateTime>>(
|
||||||
nameof(SelectedDates), new List<DateTime>());
|
nameof(SelectedDates), new List<DateTime>());
|
||||||
@@ -30,6 +33,16 @@ public class DateRangePicker : TemplatedControl
|
|||||||
set => SetValue(SelectedDatesProperty, value);
|
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 =
|
public static readonly StyledProperty<string> DisplayTextProperty =
|
||||||
AvaloniaProperty.Register<DateRangePicker, string>(
|
AvaloniaProperty.Register<DateRangePicker, string>(
|
||||||
nameof(DisplayText), "Select Date");
|
nameof(DisplayText), "Select Date");
|
||||||
@@ -40,114 +53,223 @@ public class DateRangePicker : TemplatedControl
|
|||||||
set => SetValue(DisplayTextProperty, value);
|
set => SetValue(DisplayTextProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Button _button;
|
|
||||||
private Popup _popup;
|
private Button? _button;
|
||||||
private Calendar _calendar;
|
private Popup? _popup;
|
||||||
|
private Calendar? _calendar;
|
||||||
|
|
||||||
|
|
||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
private bool _isSyncing = false;
|
||||||
{
|
|
||||||
base.OnPropertyChanged(change);
|
|
||||||
if (change.Property == SelectedDatesProperty && _calendar != null)
|
|
||||||
{
|
|
||||||
_calendar.SelectedDates.Clear();
|
|
||||||
|
|
||||||
foreach (var date in SelectedDates)
|
|
||||||
{
|
|
||||||
_calendar.SelectedDates.Add(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SelectionMode == CalendarSelectionMode.SingleDate)
|
|
||||||
_popup.IsOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
|
|
||||||
|
if (_button != null) _button.Click -= OnButtonClick;
|
||||||
|
if (_calendar != null)
|
||||||
|
{
|
||||||
|
_calendar.SelectedDatesChanged -= OnCalendarDatesChanged;
|
||||||
|
_calendar.RemoveHandler(PointerReleasedEvent, OnCalendarPointerReleased);
|
||||||
|
}
|
||||||
|
|
||||||
_button = e.NameScope.Find<Button>("PART_Button");
|
_button = e.NameScope.Find<Button>("PART_Button");
|
||||||
_popup = e.NameScope.Find<Popup>("PART_Popup");
|
_popup = e.NameScope.Find<Popup>("PART_Popup");
|
||||||
_calendar = e.NameScope.Find<Calendar>("PART_Calendar");
|
_calendar = e.NameScope.Find<Calendar>("PART_Calendar");
|
||||||
|
|
||||||
if (_button != null)
|
if (_button != null)
|
||||||
{
|
_button.Click += OnButtonClick;
|
||||||
_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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_calendar != null)
|
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.AllowTapRangeSelection = true;
|
||||||
|
|
||||||
|
|
||||||
|
_calendar.SelectedDatesChanged += OnCalendarDatesChanged;
|
||||||
|
_calendar.AddHandler(PointerReleasedEvent, OnCalendarPointerReleased, RoutingStrategies.Tunnel);
|
||||||
|
|
||||||
|
|
||||||
|
SyncToCalendar();
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateDisplayText();
|
UpdateDisplayText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCalendarPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_calendar!.SelectionMode != CalendarSelectionMode.SingleDate) return;
|
||||||
|
|
||||||
|
if (_isSyncing) return;
|
||||||
|
|
||||||
|
if (_popup is null || !_popup.IsOpen) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_popup is null) return;
|
||||||
|
|
||||||
|
|
||||||
|
SyncToCalendar();
|
||||||
|
_popup.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCalendarDatesChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_calendar!.SelectionMode == CalendarSelectionMode.SingleDate) return;
|
||||||
|
|
||||||
|
if (_isSyncing) return;
|
||||||
|
|
||||||
|
if (_popup is null || !_popup.IsOpen) return;
|
||||||
|
|
||||||
|
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()
|
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:
|
CalendarSelectionMode.SingleDate => "Select Date",
|
||||||
DisplayText = "Select Date";
|
CalendarSelectionMode.SingleRange => "Select Date Range",
|
||||||
break;
|
_ => "Select Date"
|
||||||
|
};
|
||||||
case CalendarSelectionMode.SingleRange:
|
|
||||||
DisplayText = "Select Date Range";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DisplayText = "Select Date";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ordered = SelectedDates.OrderBy(d => d).ToList();
|
var ordered = SelectedDates.OrderBy(d => d).ToList();
|
||||||
|
|
||||||
switch (SelectionMode)
|
DisplayText = SelectionMode switch
|
||||||
{
|
{
|
||||||
case CalendarSelectionMode.SingleDate:
|
CalendarSelectionMode.SingleDate => ordered[0].ToString("MMM dd, yyyy", culture),
|
||||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CalendarSelectionMode.SingleRange:
|
CalendarSelectionMode.SingleRange => ordered.Count == 1
|
||||||
if (ordered.Count == 1)
|
? ordered[0].ToString("MMM dd, yyyy", culture)
|
||||||
{
|
: $"{ordered.First().ToString("MMM dd, yyyy", culture)} → {ordered.Last().ToString("MMM dd, yyyy", culture)}",
|
||||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisplayText =
|
|
||||||
$"{ordered.First():MMM dd, yyyy} → {ordered.Last():MMM dd, yyyy}";
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
CalendarSelectionMode.MultipleRange => string.Join(", ",
|
||||||
|
ordered.Select(d => d.ToString("MMM dd, yyyy", culture))),
|
||||||
|
|
||||||
case CalendarSelectionMode.MultipleRange:
|
_ => ordered[0].ToString("MMM dd, yyyy", culture)
|
||||||
DisplayText = string.Join(", ",
|
};
|
||||||
ordered.Select(d => d.ToString("MMM dd, yyyy")));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DisplayText = ordered[0].ToString("MMM dd, yyyy");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,15 @@ public class GeneralDataRepo
|
|||||||
public Profile? Profile { get; set; }
|
public Profile? Profile { get; set; }
|
||||||
public List<Category>? Categories { get; set; }
|
public List<Category>? Categories { get; set; }
|
||||||
public List<Account>? Accounts { 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()
|
public async Task<Profile?> FetchProfileInfo()
|
||||||
{
|
{
|
||||||
if (Profile is not null) return Profile;
|
if (Profile is not null) return Profile;
|
||||||
|
|
||||||
var profile = await SupabaseService.Client.From<Profile>().Get();
|
var profile = await SupabaseService.Client.From<Profile>().Get();
|
||||||
return profile.Models.FirstOrDefault();
|
Profile = profile.Model;
|
||||||
|
return profile.Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertProfileInfo(Profile profile)
|
public async Task InsertProfileInfo(Profile profile)
|
||||||
@@ -38,11 +40,42 @@ public class GeneralDataRepo
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Transaction>> FetchTransactions()
|
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
|
try
|
||||||
{
|
{
|
||||||
var transactions = await SupabaseService.Client.From<Transaction>().Get();
|
await SupabaseService.Client.From<Transaction>().Insert(transaction);
|
||||||
return transactions.Models;
|
}
|
||||||
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -53,19 +86,10 @@ public class GeneralDataRepo
|
|||||||
|
|
||||||
public async Task<List<Category>> FetchCategories()
|
public async Task<List<Category>> FetchCategories()
|
||||||
{
|
{
|
||||||
try
|
if (Categories is not null) return Categories;
|
||||||
{
|
var categories = await SupabaseService.Client.From<Category>().Get();
|
||||||
if (Categories is not null) return Categories;
|
Categories = categories.Models;
|
||||||
var categories = await SupabaseService.Client.From<Category>().Get();
|
return categories.Models;
|
||||||
Categories = categories.Models;
|
|
||||||
// categories.Models.Select(x=>x.Icon).
|
|
||||||
return categories.Models;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Account>> FetchAccounts()
|
public async Task<List<Account>> FetchAccounts()
|
||||||
@@ -75,4 +99,78 @@ public class GeneralDataRepo
|
|||||||
Accounts = accounts.Models;
|
Accounts = accounts.Models;
|
||||||
return 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;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using LiveChartsCore.SkiaSharpView.Painting;
|
using LiveChartsCore.SkiaSharpView.Painting;
|
||||||
using SkiaSharp;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
123
Clario/MobileViews/MainViewMobile.axaml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<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:views="clr-namespace:Clario.Views"
|
||||||
|
xmlns:mobileViews="clr-namespace:Clario.MobileViews"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Clario.MobileViews.MainViewMobile"
|
||||||
|
x:DataType="vm:MainViewModel"
|
||||||
|
Classes="mobile"
|
||||||
|
x:Name="MainControl">
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid RowDefinitions="*,Auto"
|
||||||
|
Background="{DynamicResource BgBase}">
|
||||||
|
<mobileViews:TransactionFormViewMobile Grid.Row="0" Grid.RowSpan="2" ZIndex="2"
|
||||||
|
DataContext="{Binding TransactionFormViewModel}"
|
||||||
|
IsVisible="{Binding DataContext.IsTransactionFormVisible,ElementName=MainControl}">
|
||||||
|
</mobileViews:TransactionFormViewMobile>
|
||||||
|
<!-- ── 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" Command="{Binding OpenAddTransactionCommand}">
|
||||||
|
<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
440
Clario/MobileViews/TransactionFormViewMobile.axaml
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
<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:cc="clr-namespace:Clario.CustomControls"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
|
||||||
|
x:Class="Clario.MobileViews.TransactionFormViewMobile"
|
||||||
|
x:DataType="vm:TransactionFormViewModel"
|
||||||
|
Classes="mobile">
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:TransactionFormViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,*,Auto"
|
||||||
|
Background="{DynamicResource BgBase}">
|
||||||
|
|
||||||
|
<!-- ── Top bar ────────────────────────────── -->
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
ColumnDefinitions="Auto,*,Auto"
|
||||||
|
Margin="16,16,16,0">
|
||||||
|
|
||||||
|
<!-- Close -->
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Background="{DynamicResource BgSurface}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="20"
|
||||||
|
Width="36" Height="36"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Command="{Binding CancelCommand}">
|
||||||
|
<Svg Path="../Assets/Icons/x.svg"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Css="{DynamicResource SvgMuted}" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="1">
|
||||||
|
<TextBlock Text="{Binding FormTitle}"
|
||||||
|
FontSize="15"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource TextPrimary}"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
<TextBlock Text="{Binding FormSubtitle}"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextMuted}"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Delete (edit mode only) -->
|
||||||
|
<Button Grid.Column="2"
|
||||||
|
Background="#1A0808"
|
||||||
|
BorderBrush="#3A1515"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="20"
|
||||||
|
Width="36" Height="36"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
IsVisible="{Binding IsEditMode}"
|
||||||
|
Command="{Binding RequestDeleteCommand}">
|
||||||
|
<Svg Path="../Assets/Icons/trash-2.svg"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- placeholder to keep title centered when not in edit mode -->
|
||||||
|
<Border Grid.Column="2"
|
||||||
|
Width="36"
|
||||||
|
IsVisible="{Binding !IsEditMode}" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- ── Scrollable form ────────────────────── -->
|
||||||
|
<ScrollViewer Grid.Row="1"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
Margin="0,16,0,0">
|
||||||
|
<StackPanel Margin="16,0,16,16" Spacing="0">
|
||||||
|
<TextBlock Text="TYPE" Classes="label" Margin="0,0,0,6" />
|
||||||
|
<Border Background="{DynamicResource BgBase}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Padding="3"
|
||||||
|
Margin="0,0,0,20"
|
||||||
|
Height="50">
|
||||||
|
<Grid ColumnDefinitions="*,*">
|
||||||
|
<!-- Expense -->
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Classes="nav"
|
||||||
|
Classes.accented="{Binding IsExpense}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
CornerRadius="7"
|
||||||
|
Padding="0,8"
|
||||||
|
Focusable="False"
|
||||||
|
Command="{Binding SetTypeCommand}"
|
||||||
|
CommandParameter="expense">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<Svg Path="../Assets/Icons/arrow-up-right.svg"
|
||||||
|
Width="16" Height="16" />
|
||||||
|
<TextBlock Text="Expense"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<!-- Income -->
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Classes="nav"
|
||||||
|
Classes.accented="{Binding IsIncome}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
CornerRadius="7"
|
||||||
|
Padding="0,8"
|
||||||
|
Focusable="False"
|
||||||
|
Command="{Binding SetTypeCommand}"
|
||||||
|
CommandParameter="income">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<Svg Path="../Assets/Icons/arrow-down-left.svg"
|
||||||
|
Width="16" Height="16" />
|
||||||
|
<TextBlock Text="Income"
|
||||||
|
FontSize="16"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ── Amount ──────────────────────── -->
|
||||||
|
<TextBlock Text="AMOUNT" Classes="label" FontSize="14" Margin="0,0,0,6" />
|
||||||
|
<Border Background="{DynamicResource BgBase}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Padding="14,0"
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Height="64">
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="$"
|
||||||
|
FontSize="32"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource TextMuted}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Padding="0 0 0 2" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="ghost numeric"
|
||||||
|
Text="{Binding Amount, Mode=TwoWay}"
|
||||||
|
Watermark="0.00"
|
||||||
|
FontSize="32"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource TextPrimary}"
|
||||||
|
Height="54"
|
||||||
|
Padding="0 0 0 2"
|
||||||
|
VerticalContentAlignment="Center">
|
||||||
|
</TextBox>
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="{Binding Currency}"
|
||||||
|
FontSize="18"
|
||||||
|
Foreground="{DynamicResource TextDisabled}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Padding="0 0 0 2" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ── Description ─────────────────── -->
|
||||||
|
<TextBlock Text="DESCRIPTION" Classes="label" FontSize="14" Margin="0,0,0,6" />
|
||||||
|
<TextBox Text="{Binding Description, Mode=TwoWay}"
|
||||||
|
Watermark="e.g. Grocery Shopping"
|
||||||
|
FontSize="16"
|
||||||
|
Height="48"
|
||||||
|
Padding="12,0"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Margin="0,0,0,16" />
|
||||||
|
|
||||||
|
<!-- ── Category + Account ──────────── -->
|
||||||
|
<Grid ColumnDefinitions="*,14,*" Margin="0,0,0,16">
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
<StackPanel Grid.Column="0" Spacing="6">
|
||||||
|
<TextBlock Text="CATEGORY" Classes="label" FontSize="14" />
|
||||||
|
<Border Background="{DynamicResource BgBase}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}">
|
||||||
|
<Grid ColumnDefinitions="Auto,*" Height="42">
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
CornerRadius="7"
|
||||||
|
Width="36" Height="36"
|
||||||
|
Margin="3,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush
|
||||||
|
Color="{Binding SelectedCategory.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
|
||||||
|
Opacity="0.15" />
|
||||||
|
</Border.Background>
|
||||||
|
<Svg Path="{Binding SelectedCategory.Icon, Converter={StaticResource SvgPathFromName}}"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Css="{Binding SelectedCategory.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||||
|
</Border>
|
||||||
|
<ComboBox Grid.Column="1"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ItemsSource="{Binding Categories}"
|
||||||
|
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
|
||||||
|
DisplayMemberBinding="{Binding Name}"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="8,10"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Account -->
|
||||||
|
<StackPanel Grid.Column="2" Spacing="6">
|
||||||
|
<TextBlock Text="ACCOUNT" Classes="label" FontSize="14" />
|
||||||
|
<Border Background="{DynamicResource BgBase}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}">
|
||||||
|
<Grid ColumnDefinitions="Auto,*" Height="42">
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
CornerRadius="7"
|
||||||
|
Width="36" Height="36"
|
||||||
|
Margin="3,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<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="14" Height="14"
|
||||||
|
Css="{Binding SelectedAccount.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
|
||||||
|
</Border>
|
||||||
|
<ComboBox Grid.Column="1"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ItemsSource="{Binding Accounts}"
|
||||||
|
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}"
|
||||||
|
DisplayMemberBinding="{Binding Name}"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="8,10"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- ── Date ────────────────────────── -->
|
||||||
|
<TextBlock Text="DATE" Classes="label" FontSize="14" Margin="0,0,0,6" />
|
||||||
|
<Border Background="{DynamicResource BgBase}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Margin="0,0,0,16">
|
||||||
|
<Grid ColumnDefinitions="*,Auto" Height="42">
|
||||||
|
<cc:DateRangePicker Grid.Column="0"
|
||||||
|
Classes="ghost"
|
||||||
|
SelectionMode="SingleDate"
|
||||||
|
SelectedDates="{Binding Dates}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Padding="12,10" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="8,0"
|
||||||
|
Cursor="Hand"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Margin="1"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Command="{Binding SetTodayCommand}">
|
||||||
|
<TextBlock Text="Today"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{DynamicResource AccentBlue}" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ── Note ────────────────────────── -->
|
||||||
|
<TextBlock Text="NOTE (OPTIONAL)" Classes="label" FontSize="14" Margin="0,0,0,6" />
|
||||||
|
<TextBox Text="{Binding Note, Mode=TwoWay}"
|
||||||
|
Watermark="Add a note..."
|
||||||
|
FontSize="16"
|
||||||
|
Height="42"
|
||||||
|
Padding="12,0"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<!-- ── Validation error ─────────────── -->
|
||||||
|
<Border Background="{DynamicResource BadgeBgRed}"
|
||||||
|
BorderBrush="{DynamicResource AccentRed}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="10"
|
||||||
|
Padding="12,8"
|
||||||
|
Margin="0,8,0,16"
|
||||||
|
IsVisible="{Binding HasError}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Svg Path="../Assets/Icons/circle-alert.svg"
|
||||||
|
Width="13" Height="13"
|
||||||
|
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
|
||||||
|
<TextBlock Text="{Binding ErrorMessage}"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{DynamicResource AccentRed}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- ── Bottom action bar ──────────────────── -->
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Background="{DynamicResource BgSurface}"
|
||||||
|
BorderBrush="{DynamicResource BorderSubtle}"
|
||||||
|
BorderThickness="0,1,0,0"
|
||||||
|
Padding="16,12,16,20">
|
||||||
|
<Grid ColumnDefinitions="*,*">
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Classes="base"
|
||||||
|
Margin="0,0,6,0"
|
||||||
|
Padding="0,13"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
Content="Cancel"
|
||||||
|
Command="{Binding CancelCommand}" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Classes="accented"
|
||||||
|
Margin="6,0,0,0"
|
||||||
|
Padding="0,13"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
IsEnabled="{Binding IsValid}"
|
||||||
|
Command="{Binding SaveCommand}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Svg Path="../Assets/Icons/check.svg"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
|
||||||
|
<TextBlock Text="{Binding SaveButtonLabel}"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource BgBase}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<Grid Grid.Row="0" Grid.RowSpan="3" IsVisible="{Binding ShowDeleteConfirm}">
|
||||||
|
<Border Background="#50000000" />
|
||||||
|
<Border HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource BgSurface}"
|
||||||
|
BorderBrush="{DynamicResource AccentRed}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="18"
|
||||||
|
Padding="28"
|
||||||
|
Width="340"
|
||||||
|
BoxShadow="0 24 72 0 #60000000">
|
||||||
|
<StackPanel Spacing="0">
|
||||||
|
|
||||||
|
<!-- Icon -->
|
||||||
|
<Border Background="#2A0D0D"
|
||||||
|
CornerRadius="14"
|
||||||
|
Width="52" Height="52"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,0,0,16">
|
||||||
|
<Svg Path="../Assets/Icons/trash-2.svg"
|
||||||
|
Width="22" Height="22"
|
||||||
|
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock Text="Delete Transaction"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource TextPrimary}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<TextBlock Text="This action cannot be undone. The transaction will be permanently removed from your records."
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextMuted}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,0,0,24" />
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<UniformGrid Rows="1">
|
||||||
|
<Button Classes="base"
|
||||||
|
Margin="0,0,6,0"
|
||||||
|
Padding="0,11"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
FontSize="13"
|
||||||
|
Content="Cancel"
|
||||||
|
Command="{Binding CancelDeleteCommand}" />
|
||||||
|
<Button Margin="6,0,0,0"
|
||||||
|
Padding="0,11"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Background="#FF5E5E"
|
||||||
|
BorderThickness="0"
|
||||||
|
CornerRadius="{DynamicResource RadiusControl}"
|
||||||
|
Command="{Binding ConfirmDeleteCommand}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Svg Path="../Assets/Icons/trash-2.svg"
|
||||||
|
Width="13" Height="13"
|
||||||
|
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FFFFFF; }" />
|
||||||
|
<TextBlock Text="Delete"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="#FFFFFF"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</UniformGrid>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
13
Clario/MobileViews/TransactionFormViewMobile.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Clario.MobileViews;
|
||||||
|
|
||||||
|
public partial class TransactionFormViewMobile : UserControl
|
||||||
|
{
|
||||||
|
public TransactionFormViewMobile()
|
||||||
|
{
|
||||||
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Supabase.Postgrest.Attributes;
|
using Supabase.Postgrest.Attributes;
|
||||||
using Supabase.Postgrest.Models;
|
using Supabase.Postgrest.Models;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public class Account : BaseModel
|
|||||||
[Column("currency")] public string Currency { get; set; } = "USD";
|
[Column("currency")] public string Currency { get; set; } = "USD";
|
||||||
|
|
||||||
[Column("opening_balance")] public decimal OpeningBalance { get; set; }
|
[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; }
|
[Column("credit_limit")] public decimal? CreditLimit { get; set; }
|
||||||
|
|
||||||
@@ -37,12 +38,12 @@ public class Account : BaseModel
|
|||||||
|
|
||||||
[Column("color")] public string Color { get; set; } = string.Empty;
|
[Column("color")] public string Color { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int TransactionsCount { get; set; }
|
[JsonIgnore] public int TransactionsCount { get; set; }
|
||||||
public int IncomeTransactionsThisMonth { get; set; }
|
[JsonIgnore] public int IncomeTransactionsThisMonth { get; set; }
|
||||||
public int ExpenseTransactionsThisMonth { get; set; }
|
[JsonIgnore] public int ExpenseTransactionsThisMonth { get; set; }
|
||||||
public decimal TotalIncomeThisMonth { get; set; }
|
[JsonIgnore] public decimal TotalIncomeThisMonth { get; set; }
|
||||||
public decimal TotalExpenseThisMonth { get; set; }
|
[JsonIgnore] public decimal TotalExpenseThisMonth { get; set; }
|
||||||
public decimal MonthlyIncrease { get; set; }
|
[JsonIgnore] public decimal MonthlyIncrease { get; set; }
|
||||||
public List<Transaction>? RecentTransactions { get; set; }
|
[JsonIgnore] public List<Transaction>? RecentTransactions { get; set; }
|
||||||
public bool GroupHeader { get; set; } = false;
|
[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 System;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Supabase.Postgrest.Attributes;
|
using Supabase.Postgrest.Attributes;
|
||||||
using Supabase.Postgrest.Models;
|
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 System;
|
||||||
using ExCSS;
|
|
||||||
using Supabase.Postgrest.Attributes;
|
using Supabase.Postgrest.Attributes;
|
||||||
using Supabase.Postgrest.Models;
|
using Supabase.Postgrest.Models;
|
||||||
|
|
||||||
@@ -14,6 +13,7 @@ public class Profile : BaseModel
|
|||||||
[Column("currency")] public string Currency { get; set; }
|
[Column("currency")] public string Currency { get; set; }
|
||||||
[Column("theme")] public string Theme { get; set; }
|
[Column("theme")] public string Theme { get; set; }
|
||||||
[Column("language")] public string Language { 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("created_at")] public DateTime CreatedAt { get; set; }
|
||||||
[Column("updated_at")] public DateTime UpdatedAt { get; set; }
|
[Column("updated_at")] public DateTime UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Clario.Data;
|
using Clario.Data;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using Newtonsoft.Json;
|
||||||
using Supabase.Postgrest.Attributes;
|
using Supabase.Postgrest.Attributes;
|
||||||
using Supabase.Postgrest.Models;
|
using Supabase.Postgrest.Models;
|
||||||
|
|
||||||
@@ -26,11 +26,12 @@ public class Transaction : BaseModel
|
|||||||
{
|
{
|
||||||
_categoryId = value;
|
_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("amount")] public decimal Amount { get; set; }
|
||||||
|
|
||||||
[Column("type")] public string Type { get; set; } = string.Empty; // "income" or "expense"
|
[Column("type")] public string Type { get; set; } = string.Empty; // "income" or "expense"
|
||||||
@@ -43,5 +44,5 @@ public class Transaction : BaseModel
|
|||||||
|
|
||||||
[Column("created_at")] public DateTime CreatedAt { 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 Avalonia.Styling;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Clario.Services;
|
namespace Clario.Services;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
<StyleInclude Source="Styles/ToggleSwitchStyles.axaml" />
|
<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" />
|
<StyleInclude Source="../CustomControls/DateRangePicker.axaml" />
|
||||||
<Styles.Resources>
|
<Styles.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
@@ -87,7 +88,6 @@
|
|||||||
|
|
||||||
<!-- ACCENTS -->
|
<!-- ACCENTS -->
|
||||||
<SolidColorBrush x:Key="AccentBlue" Color="#7B9CFF" />
|
<SolidColorBrush x:Key="AccentBlue" Color="#7B9CFF" />
|
||||||
|
|
||||||
<SolidColorBrush x:Key="AccentGreen" Color="#2ECC8A" />
|
<SolidColorBrush x:Key="AccentGreen" Color="#2ECC8A" />
|
||||||
<SolidColorBrush x:Key="AccentYellow" Color="#F5C842" />
|
<SolidColorBrush x:Key="AccentYellow" Color="#F5C842" />
|
||||||
<SolidColorBrush x:Key="AccentRed" Color="#FF5E5E" />
|
<SolidColorBrush x:Key="AccentRed" Color="#FF5E5E" />
|
||||||
@@ -306,18 +306,21 @@
|
|||||||
<Setter Property="Background" Value="{DynamicResource BadgeBgGreen}" />
|
<Setter Property="Background" Value="{DynamicResource BadgeBgGreen}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
||||||
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentGreen}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.badge-red">
|
<Style Selector="Border.badge-red">
|
||||||
<Setter Property="Background" Value="{DynamicResource BadgeBgRed}" />
|
<Setter Property="Background" Value="{DynamicResource BadgeBgRed}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
||||||
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentRed}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.badge-yellow">
|
<Style Selector="Border.badge-yellow">
|
||||||
<Setter Property="Background" Value="{DynamicResource BadgeBgYellow}" />
|
<Setter Property="Background" Value="{DynamicResource BadgeBgYellow}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
|
||||||
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentYellow}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.badge-blue">
|
<Style Selector="Border.badge-blue">
|
||||||
@@ -463,8 +466,8 @@
|
|||||||
|
|
||||||
<Style Selector="Button.nav">
|
<Style Selector="Button.nav">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextDisabled}" />
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
<Setter Property="Svg.Css" Value="{DynamicResource SvgMuted}" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
<Setter Property="Padding" Value="{DynamicResource NavButtonPadding}" />
|
<Setter Property="Padding" Value="{DynamicResource NavButtonPadding}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||||
@@ -487,8 +490,40 @@
|
|||||||
|
|
||||||
<Style Selector="Button.nav:disabled /template/ ContentPresenter">
|
<Style Selector="Button.nav:disabled /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextDisabled}" />
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextMuted}" />
|
||||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgDisabled}" />
|
<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" />
|
<Setter Property="Opacity" Value="0.4" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@@ -513,12 +548,24 @@
|
|||||||
<Setter Property="Svg.Css" Value="{DynamicResource SvgBlue}" />
|
<Setter Property="Svg.Css" Value="{DynamicResource SvgBlue}" />
|
||||||
</Style>
|
</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 -->
|
<!-- ACCENTED BUTTON -->
|
||||||
|
|
||||||
<Style Selector="Button.accented">
|
<Style Selector="Button.accented">
|
||||||
<Setter Property="Background" Value="{DynamicResource AccentBlue}" />
|
<Setter Property="Background" Value="{DynamicResource AccentBlue}" />
|
||||||
<Setter Property="Foreground" Value="{DynamicResource BgBase}" />
|
<Setter Property="Foreground" Value="{DynamicResource BgBase}" />
|
||||||
|
<Setter Property="Svg.Css" Value="{DynamicResource SvgBase}" />
|
||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
<Setter Property="FontSize" Value="{DynamicResource FontSizeBody}" />
|
<Setter Property="FontSize" Value="{DynamicResource FontSizeBody}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||||
@@ -559,11 +606,15 @@
|
|||||||
|
|
||||||
<Style Selector="Button.base:pointerover /template/ ContentPresenter">
|
<Style Selector="Button.base:pointerover /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
<Setter Property="Background" Value="{DynamicResource BgHover}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextPrimary}" />
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextPrimary}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Button.base:pressed /template/ ContentPresenter">
|
<Style Selector="Button.base:pressed /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource BorderSubtle}" />
|
<Setter Property="Background" Value="{DynamicResource BorderSubtle}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Button.base:disabled /template/ ContentPresenter">
|
<Style Selector="Button.base:disabled /template/ ContentPresenter">
|
||||||
@@ -578,6 +629,19 @@
|
|||||||
</Style>
|
</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 -->
|
<!-- TOGGLE BUTTON -->
|
||||||
|
|
||||||
<Style Selector="ToggleButton">
|
<Style Selector="ToggleButton">
|
||||||
@@ -663,6 +727,12 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
<Style Selector=".mobile TextBox.numeric">
|
||||||
|
<Setter Property="(TextInputOptions.ContentType)" Value="Number" />
|
||||||
|
<Setter Property="(TextInputOptions.ReturnKeyType)" Value="Next" />
|
||||||
|
<Setter Property="(TextInputOptions.Multiline)" Value="False" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<Style Selector="TextBox.ghost">
|
<Style Selector="TextBox.ghost">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
@@ -685,6 +755,25 @@
|
|||||||
<Setter Property="Opacity" Value="1" />
|
<Setter Property="Opacity" Value="1" />
|
||||||
</Style>
|
</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 -->
|
<!-- COMBOBOX -->
|
||||||
|
|
||||||
<Style Selector="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">
|
<Style Selector="Calendar">
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource BgSurface}"></Setter>
|
<Setter Property="BorderBrush" Value="{DynamicResource BgSurface}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="CalendarItem">
|
<Style Selector="CalendarItem">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource CalendarViewForeground}" />
|
<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 name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||||
var type = Type.GetType(name);
|
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)
|
if (type != null)
|
||||||
{
|
{
|
||||||
return (Control)Activator.CreateInstance(type)!;
|
return (Control)Activator.CreateInstance(type)!;
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Clario.Data;
|
using Clario.Data;
|
||||||
using Clario.Models;
|
using Clario.Models;
|
||||||
using Clario.Services;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
@@ -15,30 +13,29 @@ namespace Clario.ViewModels;
|
|||||||
public partial class AccountsViewModel : ViewModelBase
|
public partial class AccountsViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public required ViewModelBase parentViewModel;
|
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 ObservableCollection<Account> _visibleAccounts = new();
|
||||||
[ObservableProperty] private decimal _totalBalance = 0;
|
[ObservableProperty] private decimal _totalBalance = 0;
|
||||||
[ObservableProperty] private Account _selectedAccount;
|
[ObservableProperty] private Account? _selectedAccount;
|
||||||
|
|
||||||
public AccountsViewModel()
|
public AccountsViewModel()
|
||||||
{
|
{
|
||||||
_ = Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
_accounts = await DataRepo.General.FetchAccounts();
|
FetchAndProcessAccountInfo();
|
||||||
await FetchAndProcessAccountInfo();
|
|
||||||
GroupAccounts();
|
GroupAccounts();
|
||||||
SelectedAccount = VisibleAccounts.First(x => !x.GroupHeader);
|
SelectedAccount = VisibleAccounts.FirstOrDefault(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.TransactionsCount = accountTransactions.Count;
|
||||||
account.CurrentBalance = account.OpeningBalance + accountTransactions.Sum(t => t.Type == "income" ? t.Amount : -t.Amount);
|
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);
|
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)
|
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())
|
if (accountsOfType.Any())
|
||||||
{
|
{
|
||||||
var header = new Account { Name = type.Value.ToUpper(), GroupHeader = true };
|
var header = new Account { Name = type.Value.ToUpper(), GroupHeader = true };
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Clario.Data;
|
|
||||||
using Clario.Models.GeneralModels;
|
|
||||||
using Clario.Services;
|
using Clario.Services;
|
||||||
using Clario.Views;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Supabase.Gotrue;
|
using Supabase.Gotrue;
|
||||||
@@ -37,6 +34,7 @@ public partial class AuthViewModel : ViewModelBase
|
|||||||
|
|
||||||
public AuthViewModel()
|
public AuthViewModel()
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("auth vm loaded");
|
||||||
setDefaults();
|
setDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
namespace Clario.ViewModels;
|
||||||
|
|
||||||
namespace Clario.ViewModels;
|
|
||||||
|
|
||||||
public partial class BudgetCardMenuViewModel : ViewModelBase
|
public partial class BudgetCardMenuViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
namespace Clario.ViewModels;
|
||||||
|
|
||||||
namespace Clario.ViewModels;
|
|
||||||
|
|
||||||
public partial class BudgetFormViewModel : ViewModelBase
|
public partial class BudgetFormViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
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.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using LiveChartsCore;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.Kernel;
|
using LiveChartsCore.SkiaSharpView.Avalonia;
|
||||||
using LiveChartsCore.SkiaSharpView;
|
|
||||||
using LiveChartsCore.SkiaSharpView.Painting;
|
using LiveChartsCore.SkiaSharpView.Painting;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
@@ -14,39 +20,123 @@ namespace Clario.ViewModels;
|
|||||||
public partial class BudgetViewModel : ViewModelBase
|
public partial class BudgetViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public required ViewModelBase parentViewModel;
|
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 =
|
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(NextPeriodCommand), nameof(PreviousPeriodCommand))]
|
||||||
[
|
private DateTime _currentPeriod = DateTime.Now.Date;
|
||||||
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 }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class PieData : ObservableObject
|
public bool CanGoToNextPeriod => CurrentPeriod.Month < DateTime.Now.Month;
|
||||||
{
|
public bool CanGoToPreviousPeriod => Transactions.Any() && CurrentPeriod.Month > Transactions.Min(x => x.Date.Month);
|
||||||
[ObservableProperty] private string _name;
|
public string CurrentPeriodFormatted => CurrentPeriod.ToString("MMMM yyyy");
|
||||||
[ObservableProperty] private double[] _values;
|
|
||||||
[ObservableProperty] private SolidColorPaint _fill;
|
|
||||||
[ObservableProperty] private IBrush _bg;
|
|
||||||
[ObservableProperty] private double _innerRadius = 60;
|
|
||||||
[ObservableProperty] private Func<ChartPoint, string> _formatter;
|
|
||||||
|
|
||||||
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 =>
|
try
|
||||||
{
|
{
|
||||||
var pct = point.StackedValue!.Share * 100;
|
await ProcessBudgets();
|
||||||
return $"${point.Coordinate.PrimaryValue:N0} ({pct:N1}%)";
|
ProcessChartData();
|
||||||
};
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Security.Authentication;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Clario.Data;
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Skia;
|
|
||||||
using Avalonia.Styling;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Clario.Extensions;
|
using Clario.Models;
|
||||||
using Clario.Services;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.Kernel;
|
using LiveChartsCore.SkiaSharpView;
|
||||||
using LiveChartsCore.SkiaSharpView.Painting;
|
using LiveChartsCore.SkiaSharpView.Painting;
|
||||||
using ShimSkiaSharp;
|
|
||||||
using SKColor = SkiaSharp.SKColor;
|
using SKColor = SkiaSharp.SKColor;
|
||||||
|
|
||||||
namespace Clario.ViewModels;
|
namespace Clario.ViewModels;
|
||||||
@@ -20,13 +18,104 @@ namespace Clario.ViewModels;
|
|||||||
public partial class DashboardViewModel : ViewModelBase
|
public partial class DashboardViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public required ViewModelBase parentViewModel;
|
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 int MaxChartWidth => SpendingByCategoryChartData.Count * 150;
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
public bool HasSpendingData => SpendingByCategoryChartData.Any();
|
||||||
|
public bool HasBudgetData => BudgetsTrackerData.Any();
|
||||||
|
public bool HasTransactionData => RecentTransactions.Any();
|
||||||
|
|
||||||
|
[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()
|
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 == 0) ? 1 : lastMonthIncome)) - 1, 2);
|
||||||
|
_monthlyExpensesChange = Math.Round((MonthlyExpenses / ((lastMonthExpenses == 0) ? 1 : lastMonthExpenses)) - 1, 2);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(MonthlyIncomeChangeFormatted));
|
||||||
|
OnPropertyChanged(nameof(MonthlyExpenseChangeFormatted));
|
||||||
|
|
||||||
|
UpdateSpendingByCategoryChart();
|
||||||
|
_ = UpdateBudgetTracker();
|
||||||
|
UpdateRecentTransactions();
|
||||||
|
UpdateAccountsSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -41,15 +130,94 @@ public partial class DashboardViewModel : ViewModelBase
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void CreateTransaction()
|
private void CreateTransaction()
|
||||||
{
|
{
|
||||||
|
((MainViewModel)parentViewModel).OpenAddTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSpendingByCategoryChart(ChartTimePeriod period = ChartTimePeriod.ThisMonth)
|
||||||
|
{
|
||||||
|
var tempList = new List<ColumnChartData>();
|
||||||
|
|
||||||
|
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 = 150
|
||||||
|
}).ToArray();
|
||||||
|
OnPropertyChanged(nameof(HasSpendingData));
|
||||||
|
OnPropertyChanged(nameof(MaxChartWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
OnPropertyChanged(nameof(HasBudgetData));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRecentTransactions()
|
||||||
|
{
|
||||||
|
RecentTransactions = new ObservableCollection<Transaction>(Transactions.OrderByDescending(x => x.Date).Take(5));
|
||||||
|
OnPropertyChanged(nameof(HasTransactionData));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ChartData : ObservableObject
|
|
||||||
{
|
|
||||||
[ObservableProperty] private string _name;
|
|
||||||
[ObservableProperty] private double[] _values;
|
|
||||||
[ObservableProperty] private SolidColorPaint _fill;
|
|
||||||
|
|
||||||
public Func<ChartPoint, string> ToolTipFormatter => point => $"${point.Coordinate.PrimaryValue:N0}";
|
|
||||||
}
|
|
||||||
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;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Clario.Data;
|
using Clario.Data;
|
||||||
|
using Clario.Models;
|
||||||
using Clario.Models.GeneralModels;
|
using Clario.Models.GeneralModels;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
@@ -12,11 +17,19 @@ namespace Clario.ViewModels;
|
|||||||
|
|
||||||
public partial class MainViewModel : ViewModelBase
|
public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public DashboardViewModel _dashboardViewModel;
|
private DashboardViewModel _dashboardViewModel;
|
||||||
public TransactionsViewModel _transactionsViewModel;
|
public TransactionsViewModel _transactionsViewModel;
|
||||||
public AccountsViewModel _accountsViewModel;
|
private AccountsViewModel _accountsViewModel;
|
||||||
public BudgetViewModel _budgetViewModel;
|
private BudgetViewModel _budgetViewModel;
|
||||||
|
[ObservableProperty] private TransactionFormViewModel _transactionFormViewModel;
|
||||||
[ObservableProperty] public Profile? _profile;
|
[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))]
|
[ObservableProperty] [NotifyPropertyChangedFor(nameof(isOnDashboard), nameof(isOnTransactions), nameof(isOnAccounts), nameof(isOnBudget))]
|
||||||
private ViewModelBase? _currentView;
|
private ViewModelBase? _currentView;
|
||||||
@@ -25,24 +38,174 @@ public partial class MainViewModel : ViewModelBase
|
|||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
_dashboardViewModel = new DashboardViewModel() { parentViewModel = this };
|
Console.WriteLine("main vm loaded");
|
||||||
_transactionsViewModel = new TransactionsViewModel() { parentViewModel = this };
|
CurrentView = new LoadingViewModel();
|
||||||
_accountsViewModel = new AccountsViewModel() { parentViewModel = this };
|
|
||||||
_budgetViewModel = new BudgetViewModel() { parentViewModel = this };
|
|
||||||
CurrentView = _dashboardViewModel;
|
|
||||||
// CurrentView = _transactionsViewModel;
|
|
||||||
IsDarkTheme = ThemeService.IsDarkTheme;
|
|
||||||
|
|
||||||
_ = InitializeApp();
|
_ = InitializeApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task InitializeApp()
|
private async Task InitializeApp()
|
||||||
{
|
{
|
||||||
Profile = await DataRepo.General.FetchProfileInfo();
|
try
|
||||||
_ = await DataRepo.General.FetchCategories();
|
{
|
||||||
_ = await DataRepo.General.FetchAccounts();
|
var profilesTask = DataRepo.General.FetchProfileInfo();
|
||||||
ThemeService.SwitchToTheme(Profile?.Theme ?? "system");
|
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;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TransactionFormViewModel.SetupForAdd(
|
||||||
|
new ObservableCollection<Category>(_categories),
|
||||||
|
new ObservableCollection<Account>(_accounts)
|
||||||
|
);
|
||||||
|
TransactionFormViewModel.OnSaved = () =>
|
||||||
|
{
|
||||||
|
if (TransactionFormViewModel.ResultTransaction is not null)
|
||||||
|
{
|
||||||
|
var previousItem = _transactionsViewModel.AllTransactions.FirstOrDefault(x => x.Date < TransactionFormViewModel.ResultTransaction.Date);
|
||||||
|
var index = 0;
|
||||||
|
if (previousItem is not null)
|
||||||
|
index = _transactionsViewModel.AllTransactions.IndexOf(previousItem);
|
||||||
|
if (index == -1) index = 0;
|
||||||
|
_transactionsViewModel.AllTransactions.Insert(index, TransactionFormViewModel.ResultTransaction);
|
||||||
|
_dashboardViewModel.Transactions.Insert(index, TransactionFormViewModel.ResultTransaction);
|
||||||
|
_dashboardViewModel.UpdateUserOverviewCommand.Execute(null);
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[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]
|
[RelayCommand]
|
||||||
|
|||||||
261
Clario/ViewModels/TransactionFormViewModel.cs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _showDeleteConfirm = false;
|
||||||
|
|
||||||
|
// ── Edit mode: original transaction ─────────────────────
|
||||||
|
private Transaction? _editingTransaction;
|
||||||
|
private Guid? _editingId;
|
||||||
|
|
||||||
|
// ── Result transaction ──────────────────────────────────
|
||||||
|
public Transaction? ResultTransaction { get; set; }
|
||||||
|
|
||||||
|
// ── Commands ────────────────────────────────────────────
|
||||||
|
|
||||||
|
partial void OnSelectedCategoryChanged(Category? value)
|
||||||
|
{
|
||||||
|
if (value.Type == Type) return;
|
||||||
|
Type = value.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnTypeChanged(string value)
|
||||||
|
{
|
||||||
|
if (value == SelectedCategory?.Type) return;
|
||||||
|
SelectedCategory = _categories.FirstOrDefault(c => c.Type == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 ConfirmDelete()
|
||||||
|
{
|
||||||
|
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 RequestDelete()
|
||||||
|
{
|
||||||
|
ShowDeleteConfirm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CancelDelete()
|
||||||
|
{
|
||||||
|
ShowDeleteConfirm = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
ShowDeleteConfirm = false;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
ShowDeleteConfirm = false;
|
||||||
|
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.Messages;
|
||||||
using Clario.Models;
|
using Clario.Models;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel.__Internals;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
|
||||||
@@ -18,8 +17,11 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
public required ViewModelBase parentViewModel;
|
public required ViewModelBase parentViewModel;
|
||||||
|
|
||||||
private List<Transaction> _allTransactions = new();
|
public List<Transaction> AllTransactions = new();
|
||||||
private List<Transaction> _filteredTransactions = new();
|
[ObservableProperty] private ObservableCollection<Category> _categories = new();
|
||||||
|
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
|
||||||
|
|
||||||
|
[ObservableProperty] private List<Transaction> _filteredTransactions = new();
|
||||||
|
|
||||||
private int _pageSize = 25;
|
private int _pageSize = 25;
|
||||||
[ObservableProperty] private int _pageSizeIndex = 0;
|
[ObservableProperty] private int _pageSizeIndex = 0;
|
||||||
@@ -30,8 +32,6 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
[ObservableProperty] private string _paginationSummaryText;
|
[ObservableProperty] private string _paginationSummaryText;
|
||||||
|
|
||||||
[ObservableProperty] private ObservableCollection<Transaction> _pagedTransactions = new();
|
[ObservableProperty] private ObservableCollection<Transaction> _pagedTransactions = new();
|
||||||
[ObservableProperty] private ObservableCollection<Category> _categories = new();
|
|
||||||
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
|
|
||||||
|
|
||||||
[ObservableProperty] private ObservableCollection<string> _sortOptions = new()
|
[ObservableProperty] private ObservableCollection<string> _sortOptions = new()
|
||||||
{
|
{
|
||||||
@@ -84,7 +84,6 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
|
|
||||||
public TransactionsViewModel()
|
public TransactionsViewModel()
|
||||||
{
|
{
|
||||||
_ = Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnPageSizeIndexChanged(int value)
|
partial void OnPageSizeIndexChanged(int value)
|
||||||
@@ -141,6 +140,7 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
GroupTransactions();
|
GroupTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
private void ApplyFilters()
|
private void ApplyFilters()
|
||||||
{
|
{
|
||||||
// Console.WriteLine($"Search Text: {_searchText}");
|
// Console.WriteLine($"Search Text: {_searchText}");
|
||||||
@@ -149,7 +149,7 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
// Console.WriteLine($"Transaction Type: {_transactionType}");
|
// Console.WriteLine($"Transaction Type: {_transactionType}");
|
||||||
|
|
||||||
|
|
||||||
var filtered = _allTransactions.Where(x =>
|
var filtered = AllTransactions.Where(x =>
|
||||||
x.Description.Contains(_searchText, StringComparison.OrdinalIgnoreCase)
|
x.Description.Contains(_searchText, StringComparison.OrdinalIgnoreCase)
|
||||||
|| x.Note.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]
|
[RelayCommand]
|
||||||
@@ -271,14 +271,25 @@ public partial class TransactionsViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void GroupTransactions()
|
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)
|
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;
|
string label;
|
||||||
var culture = new CultureInfo("en-US");
|
var culture = new CultureInfo("en-US");
|
||||||
if (date.Day == DateTime.Now.Day) label = "Today - " + date.ToString("MMM dd", culture);
|
if (date.Date == DateTime.Now.Date) label = "Today - " + date.ToString("MMM dd", culture);
|
||||||
else if (date.Day == DateTime.Now.AddDays(-1).Day) label = "Yesterday - " + 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);
|
else label = date.ToString("MMM dd, yyyy", culture);
|
||||||
var header = new Transaction { Description = label, Date = date, GroupHeader = true };
|
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
|
try
|
||||||
{
|
{
|
||||||
await FetchAndInitializeCategories();
|
InitializeCategories();
|
||||||
await FetchAndInitializeAccounts();
|
InitializeAccounts();
|
||||||
|
|
||||||
|
|
||||||
var transactions = await DataRepo.General.FetchTransactions();
|
|
||||||
_allTransactions = transactions.OrderByDescending(x => x.Date).ToList();
|
|
||||||
|
|
||||||
CalculateMonthlyFinancials();
|
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" });
|
Categories.Insert(0, new Category() { Name = "All Categories" });
|
||||||
SelectedCategory = Categories.First();
|
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" });
|
Accounts.Insert(0, new Account() { Name = "All Accounts" });
|
||||||
SelectedAccount = Accounts.First();
|
SelectedAccount = Accounts.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateMonthlyFinancials()
|
private void CalculateMonthlyFinancials()
|
||||||
{
|
{
|
||||||
TotalExpenses = _allTransactions.Where(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
|
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));
|
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);
|
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);
|
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)
|
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);
|
return list.GetRange(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CreateTransaction()
|
||||||
|
{
|
||||||
|
((MainViewModel)parentViewModel).OpenAddTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void EditTransaction(Transaction transaction)
|
||||||
|
{
|
||||||
|
((MainViewModel)parentViewModel).OpenEditTransaction(transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||