From e7dc369c4fd98202d7d10307e4c19d0edcd74796 Mon Sep 17 00:00:00 2001
From: Hyperling <me@hyperling.com>
Date: Sat, 8 Mar 2025 08:33:31 -0700
Subject: [PATCH] Projections are working! Just 1 issue noticed so far.

---
 lib/pages/report.dart | 141 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 129 insertions(+), 12 deletions(-)

diff --git a/lib/pages/report.dart b/lib/pages/report.dart
index f211fb8..abae849 100644
--- a/lib/pages/report.dart
+++ b/lib/pages/report.dart
@@ -7,9 +7,16 @@ import '/db.dart';
 import '/models/tracked_item.dart';
 
 /// TODO:
-/// - Projected Assets in:
-///     - 1 week, 1 month, 1 quarter, 1 year
-///     - 1/2 year? 2 years? 5 years? Allow customization?
+/// - Projected Assets:
+///   - Allow customization?
+///   - Fix bug where editing an item does not reflect immediately when returning to Reports page.
+///     - Currently reflects after going back to Reports the 2nd time.
+
+double _assetTotal = 0,
+    _expenseMonthly = 0,
+    _expenseYearly = 0,
+    _incomeMonthly = 0,
+    _incomeYearly = 0;
 
 class ProjectionPage extends StatefulWidget {
   const ProjectionPage({
@@ -23,6 +30,7 @@ class ProjectionPage extends StatefulWidget {
 class _ProjectionPageState extends State<ProjectionPage> {
   @override
   Widget build(BuildContext context) {
+    // Summaries for display as well as calculation of totals for projections.
     Widget expenseSummary = SummaryCardForTotals(
       list: DatabaseHelper.instance.getExpenses(),
       summaryTypeLabel: ItemType.expense.title,
@@ -36,16 +44,93 @@ class _ProjectionPageState extends State<ProjectionPage> {
       summaryTypeLabel: ItemType.asset.title,
     );
 
+    // Calculations for the projections.
+    double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
+        threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
+        sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
+        oneYear = _assetTotal + (_incomeYearly - _expenseYearly),
+        twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)),
+        fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly));
+
+    // Widgets to show the projections.
+    Widget proj1 = SummaryCard(
+      name: "One month from now...",
+      leftText: "",
+      middleText: oneMonth.toStringAsFixed(2),
+      rightText: "",
+    );
+    Widget proj2 = SummaryCard(
+      name: "Three months from now...",
+      leftText: "",
+      middleText: threeMonths.toStringAsFixed(2),
+      rightText: "",
+    );
+    Widget proj3 = SummaryCard(
+      name: "Half a year from now...",
+      leftText: "",
+      middleText: sixMonths.toStringAsFixed(2),
+      rightText: "",
+    );
+    Widget proj4 = SummaryCard(
+      name: "One year from now...",
+      leftText: "",
+      middleText: oneYear.toStringAsFixed(2),
+      rightText: "",
+    );
+    Widget proj5 = SummaryCard(
+      name: "Two years from now...",
+      leftText: "",
+      middleText: twoYears.toStringAsFixed(2),
+      rightText: "",
+    );
+    Widget proj6 = SummaryCard(
+      name: "Five years from now...",
+      leftText: "",
+      middleText: fiveYears.toStringAsFixed(2),
+      rightText: "",
+    );
+
+    // Return all of the UI elements.
     return ListView(
       children: [
+        TitleCard(title: "Summaries"),
         expenseSummary,
         incomeSummary,
         assetSummary,
+        TitleCard(title: "Projections"),
+        proj1,
+        proj2,
+        proj3,
+        proj4,
+        proj5,
+        proj6,
       ],
     );
   }
 }
 
+class TitleCard extends StatelessWidget {
+  const TitleCard({
+    super.key,
+    required this.title,
+  });
+
+  final String title;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.all(8.0),
+      child: Center(
+        child: Text(
+          title,
+          style: TextStyle(fontSize: 20),
+        ),
+      ),
+    );
+  }
+}
+
 class SummaryCardForTotals extends StatelessWidget {
   const SummaryCardForTotals({
     super.key,
@@ -70,11 +155,16 @@ class SummaryCardForTotals extends StatelessWidget {
 
           // Calculate the total fields based on item type.
           double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
-          bool isAsset = false;
+          ItemType? itemType;
           for (TrackedItem e in snapshot.data!) {
+            if (itemType == null) {
+              itemType = e.type!;
+            } else if (itemType != e.type) {
+              throw "List in SummaryCardForTotals has multiple item types, abort!";
+            }
+
             if (e.type == ItemType.asset) {
               monthlyTotal += e.amount;
-              isAsset = true;
             } else {
               dailyTotal += e.calcComparableAmountDaily();
               monthlyTotal += e.calcComparableAmountYearly() / 12;
@@ -82,11 +172,33 @@ class SummaryCardForTotals extends StatelessWidget {
             }
           }
 
+          /* Load page variables based on calculated totals. */
+          switch (itemType) {
+            case ItemType.asset:
+              _assetTotal = monthlyTotal;
+              break;
+
+            case ItemType.expense:
+              _expenseMonthly = monthlyTotal;
+              _expenseYearly = yearlyTotal;
+              break;
+
+            case ItemType.income:
+              _incomeMonthly = monthlyTotal;
+              _incomeYearly = yearlyTotal;
+              break;
+
+            default:
+              throw UnimplementedError(
+                "Item type ${itemType!.title} not handled in SummaryCardForTotals!",
+              );
+          }
+
           /* Determine what needs displayed for the item type. */
           // Header
           String plural = snapshot.data!.length == 1 ? "" : "s";
           String header = "$summaryTypeLabel Total";
-          header += isAsset ? "" : "s";
+          header += itemType == ItemType.asset ? "" : "s";
           header += " (${snapshot.data!.length} Item$plural)";
 
           // Total Fields
@@ -97,7 +209,7 @@ class SummaryCardForTotals extends StatelessWidget {
               yearlyEstimate =
                   yearlyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~";
           String leftText = "", middleText = "", rightText = "";
-          if (isAsset) {
+          if (itemType == ItemType.asset) {
             middleText = "$monthlyEstimate${monthlyTotal.toStringAsFixed(2)}";
           } else {
             leftText = "$dailyEstimate${dailyTotal.toStringAsFixed(2)} Daily";
@@ -118,7 +230,7 @@ class SummaryCardForTotals extends StatelessWidget {
   }
 }
 
-class SummaryCard extends StatelessWidget {
+class SummaryCard extends StatefulWidget {
   const SummaryCard({
     super.key,
     required this.name,
@@ -132,6 +244,11 @@ class SummaryCard extends StatelessWidget {
   final String middleText;
   final String rightText;
 
+  @override
+  State<SummaryCard> createState() => _SummaryCardState();
+}
+
+class _SummaryCardState extends State<SummaryCard> {
   @override
   Widget build(BuildContext context) {
     return Card(
@@ -143,7 +260,7 @@ class SummaryCard extends StatelessWidget {
           child: Column(
             children: [
               Text(
-                name,
+                widget.name,
                 style: TextStyle(
                   decoration: TextDecoration.underline,
                   fontSize: 16,
@@ -155,19 +272,19 @@ class SummaryCard extends StatelessWidget {
                     flex: 3,
                   ),
                   Text(
-                    leftText,
+                    widget.leftText,
                   ),
                   Spacer(
                     flex: 1,
                   ),
                   Text(
-                    middleText,
+                    widget.middleText,
                   ),
                   Spacer(
                     flex: 1,
                   ),
                   Text(
-                    rightText,
+                    widget.rightText,
                   ),
                   Spacer(
                     flex: 3,