Add feature batch 2, subscription/recurring sync, smooth charts, and app icon

- Implement subscriptions view with bidirectional recurring transaction sync
- Add cascade delete/pause/resume between subscriptions and recurring
- Fix foreign key constraints when deleting recurring transactions
- Add cross-view instant refresh via callback pattern
- Replace Bezier chart smoothing with Fritsch-Carlson monotone Hermite interpolation
- Smooth budget sparklines using shared monotone_subdivide function
- Add vertical spacing to budget rows
- Add app icon (receipt on GNOME blue) in all sizes for desktop, web, and AppImage
- Add calendar, credit cards, forecast, goals, insights, and wishlist views
- Add date picker, numpad, quick-add, category combo, and edit dialog components
- Add import/export for CSV, JSON, OFX, QIF formats
- Add NLP transaction parsing, OCR receipt scanning, expression evaluator
- Add notification support, Sankey chart, tray icon
- Add demo data seeder with full DB wipe
- Expand database schema with subscriptions, goals, credit cards, and more
This commit is contained in:
2026-03-03 21:18:37 +02:00
parent 773dae4684
commit 10a76e3003
10102 changed files with 108019 additions and 1335 deletions

View File

@@ -7,7 +7,8 @@ edition.workspace = true
outlay-core = { path = "../outlay-core" }
gtk = { package = "gtk4", version = "0.11", features = ["v4_10"] }
adw = { package = "libadwaita", version = "0.9", features = ["v1_8"] }
cairo = { package = "cairo-rs", version = "0.22", features = ["png"] }
chrono = "0.4"
gdk = { package = "gdk4", version = "0.11" }
plotters = { version = "0.3", default-features = false, features = ["bitmap_backend", "bitmap_encoder", "line_series", "area_series"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
ksni = { version = "0.3", features = ["tokio"] }

3
outlay-gtk/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-lib=fontconfig");
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { background: transparent; width: 100%; height: 100%; }
body {
background-image:
linear-gradient(45deg, #ccc 25%, transparent 25%),
linear-gradient(-45deg, #ccc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(-45deg, transparent 75%, #ccc 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.icon-wrapper {
padding: 50px;
filter: drop-shadow(0 8px 14px rgba(26, 95, 180, 0.45));
}
.icon-container {
width: 512px;
height: 512px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
/* GNOME-style rounded square - ~22% radius */
border-radius: 22%;
/* GNOME blue gradient - wider range for visible depth */
background: linear-gradient(180deg, #62a0ea 0%, #1a5fb4 55%, #144a8a 100%);
}
/* Sharp top highlight + bottom shadow edges */
.icon-container::before {
content: '';
position: absolute;
inset: 0;
border-radius: 22%;
border-top: 4px solid rgba(255,255,255,0.2);
border-bottom: 40px solid rgba(0,0,0,0.12);
border-left: none;
border-right: none;
pointer-events: none;
z-index: 2;
}
.icon {
font-size: 286px;
line-height: 1;
color: #ffffff;
position: relative;
z-index: 1;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
}
</style>
</head>
<body>
<div class="icon-wrapper">
<div class="icon-container" id="icon-target">
<i class="icon fa-solid fa-receipt"></i>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
tabler-wallet-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-wallet.svg

View File

@@ -0,0 +1 @@
tabler-calendar-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-calendar.svg

View File

@@ -0,0 +1 @@
tabler-chart-pie-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-chart-pie.svg

View File

@@ -0,0 +1 @@
tabler-credit-card-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-credit-card.svg

View File

@@ -0,0 +1 @@
tabler-trash-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-trash.svg

View File

@@ -0,0 +1 @@
tabler-download-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-download.svg

View File

@@ -0,0 +1 @@
tabler-trending-up-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-trending-up.svg

View File

@@ -0,0 +1 @@
tabler-target-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-target.svg

View File

@@ -0,0 +1 @@
tabler-history-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-history.svg

View File

@@ -0,0 +1 @@
tabler-upload-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-upload.svg

View File

@@ -0,0 +1 @@
tabler-bulb-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-bulb.svg

View File

@@ -0,0 +1 @@
tabler-receipt-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-receipt.svg

View File

@@ -0,0 +1 @@
tabler-chevron-right-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-chevron-right.svg

View File

@@ -0,0 +1 @@
tabler-calculator-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-calculator.svg

View File

@@ -0,0 +1 @@
tabler-chevron-left-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-chevron-left.svg

View File

@@ -0,0 +1 @@
tabler-repeat-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-repeat.svg

View File

@@ -0,0 +1 @@
tabler-settings-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-settings.svg

View File

@@ -0,0 +1 @@
tabler-credit-card-pay-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-credit-card-pay.svg

View File

@@ -0,0 +1 @@
tabler-shopping-cart-symbolic.svg

View File

@@ -0,0 +1 @@
tabler-shopping-cart.svg

View File

@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M16 21h3c.81 0 1.48 -.67 1.48 -1.48l.02 -.02c0 -.82 -.69 -1.5 -1.5 -1.5h-3v3" />
<path fill="none" d="M16 15h2.5c.84 -.01 1.5 .66 1.5 1.5s-.66 1.5 -1.5 1.5h-2.5v-3" />
<path fill="none" d="M4 9v-4c0 -1.036 .895 -2 2 -2s2 .964 2 2v4" />
<path fill="none" d="M2.99 11.98a9 9 0 0 0 9 9m9 -9a9 9 0 0 0 -9 -9" />
<path fill="none" d="M8 7h-4" />
</svg>

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -0,0 +1 @@
tabler-a-b-2-symbolic.svg

View File

@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 16v-5.5a2.5 2.5 0 0 1 5 0v5.5m0 -4h-5" />
<path fill="none" d="M12 12v6" />
<path fill="none" d="M12 6v2" />
<path fill="none" d="M16 8h3a2 2 0 1 1 0 4h-3m3 0a2 2 0 0 1 .83 3.82m-3.83 -3.82v-4" />
<path fill="none" d="M3 3l18 18" />
</svg>

After

Width:  |  Height:  |  Size: 470 B

View File

@@ -0,0 +1 @@
tabler-a-b-off-symbolic.svg

View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 16v-5.5a2.5 2.5 0 0 1 5 0v5.5m0 -4h-5" />
<path fill="none" d="M12 6l0 12" />
<path fill="none" d="M16 16v-8h3a2 2 0 0 1 0 4h-3m3 0a2 2 0 0 1 0 4h-3" />
</svg>

After

Width:  |  Height:  |  Size: 386 B

View File

@@ -0,0 +1 @@
tabler-a-b-symbolic.svg

View File

@@ -0,0 +1,24 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M5 5v16" />
<path fill="none" d="M19 21v-2m0 -4v-12" />
<path fill="none" d="M5 7h2m4 0h8" />
<path fill="none" d="M5 15h10" />
<path fill="none" d="M8 13v4" />
<path fill="none" d="M11 13v4" />
<path fill="none" d="M16 16v1" />
<path fill="none" d="M14 5v4" />
<path fill="none" d="M11 5v2" />
<path fill="none" d="M8 8v1" />
<path fill="none" d="M3 21h18" />
<path fill="none" d="M3 3l18 18" />
</svg>

After

Width:  |  Height:  |  Size: 645 B

View File

@@ -0,0 +1 @@
tabler-abacus-off-symbolic.svg

View File

@@ -0,0 +1,23 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M5 3v18" />
<path fill="none" d="M19 21v-18" />
<path fill="none" d="M5 7h14" />
<path fill="none" d="M5 15h14" />
<path fill="none" d="M8 13v4" />
<path fill="none" d="M11 13v4" />
<path fill="none" d="M16 13v4" />
<path fill="none" d="M14 5v4" />
<path fill="none" d="M11 5v4" />
<path fill="none" d="M8 5v4" />
<path fill="none" d="M3 21h18" />
</svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@@ -0,0 +1 @@
tabler-abacus-symbolic.svg

View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 16v-6a2 2 0 1 1 4 0v6" />
<path fill="none" d="M3 13h4" />
<path fill="none" d="M10 8v6a2 2 0 1 0 4 0v-1a2 2 0 1 0 -4 0v1" />
<path fill="none" d="M20.732 12a2 2 0 0 0 -3.732 1v1a2 2 0 0 0 3.726 1.01" />
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@@ -0,0 +1 @@
tabler-abc-symbolic.svg

View File

@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 3l18 18" />
<path fill="none" d="M14.828 9.172a4 4 0 0 1 1.172 2.828" />
<path fill="none" d="M17.657 6.343a8 8 0 0 1 1.635 8.952" />
<path fill="none" d="M9.168 14.828a4 4 0 0 1 0 -5.656" />
<path fill="none" d="M6.337 17.657a8 8 0 0 1 0 -11.314" />
</svg>

After

Width:  |  Height:  |  Size: 488 B

View File

@@ -0,0 +1 @@
tabler-access-point-off-symbolic.svg

View File

@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M12 12l0 .01" />
<path fill="none" d="M14.828 9.172a4 4 0 0 1 0 5.656" />
<path fill="none" d="M17.657 6.343a8 8 0 0 1 0 11.314" />
<path fill="none" d="M9.168 14.828a4 4 0 0 1 0 -5.656" />
<path fill="none" d="M6.337 17.657a8 8 0 0 1 0 -11.314" />
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1 @@
tabler-access-point-symbolic.svg

View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M10 16.5l2 -3l2 3m-2 -3v-1.5m2.627 -1.376l.373 -.124m-6 0l2.231 .744" />
<path fill="none" d="M20.042 16.045a9 9 0 0 0 -12.087 -12.087m-2.318 1.677a9 9 0 1 0 12.725 12.73" />
<path fill="none" d="M12 8a.5 .5 0 1 0 -.5 -.5" />
<path fill="none" d="M3 3l18 18" />
</svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1 @@
tabler-accessible-off-symbolic.svg

View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path fill="none" d="M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1" />
<path fill="none" d="M11.5 7.5a.5 .5 0 1 0 1 0a.5 .5 0 1 0 -1 0" fill="#222222" />
</svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -0,0 +1 @@
tabler-accessible-symbolic.svg

View File

@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 12h4.5l1.5 -6l4 12l2 -9l1.5 3h4.5" />
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@@ -0,0 +1 @@
tabler-activity-heartbeat-symbolic.svg

View File

@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M3 12h4l3 8l4 -16l3 8h4" />
</svg>

After

Width:  |  Height:  |  Size: 254 B

View File

@@ -0,0 +1 @@
tabler-activity-symbolic.svg

View File

@@ -0,0 +1,18 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M11.933 5h-6.933v16h13v-8" />
<path fill="none" d="M14 17h-5" />
<path fill="none" d="M9 13h5v-4h-5v4" />
<path fill="none" d="M15 5v-2" />
<path fill="none" d="M18 6l2 -2" />
<path fill="none" d="M19 9h2" />
</svg>

After

Width:  |  Height:  |  Size: 445 B

View File

@@ -0,0 +1 @@
tabler-ad-2-symbolic.svg

View File

@@ -0,0 +1,20 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M4.91 4.949a9.968 9.968 0 0 0 -2.91 7.051c0 5.523 4.477 10 10 10a9.968 9.968 0 0 0 7.05 -2.909" />
<path fill="none" d="M20.778 16.793a9.955 9.955 0 0 0 1.222 -4.793c0 -5.523 -4.477 -10 -10 -10c-1.74 0 -3.376 .444 -4.8 1.225" />
<path fill="none" d="M7 15v-4.5a1.5 1.5 0 0 1 2.138 -1.358" />
<path fill="none" d="M9.854 9.853c.094 .196 .146 .415 .146 .647v4.5" />
<path fill="none" d="M7 13h3" />
<path fill="none" d="M14 14v1h1" />
<path fill="none" d="M17 13v-2a2 2 0 0 0 -2 -2h-1v1" />
<path fill="none" d="M3 3l18 18" />
</svg>

After

Width:  |  Height:  |  Size: 765 B

View File

@@ -0,0 +1 @@
tabler-ad-circle-off-symbolic.svg

View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#222222"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path fill="none" d="M2 12a10 10 0 1 0 20 0a10 10 0 1 0 -20 0" />
<path fill="none" d="M7 15v-4.5a1.5 1.5 0 0 1 3 0v4.5" />
<path fill="none" d="M7 13h3" />
<path fill="none" d="M14 9v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1" />
</svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
tabler-ad-circle-symbolic.svg

Some files were not shown because too many files have changed in this diff Show More