Version 1.0.1

This commit is contained in:
2025-01-25 00:27:02 +01:00
parent 61ddc095b3
commit afda59570e
173 changed files with 11530 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
namespace WheresMyMoney.Maui.Core;
public record Balance(int Id, decimal Amount, DateTime Date);

View File

@@ -0,0 +1,78 @@
namespace WheresMyMoney.Maui.Core;
public enum ReoccurenceType
{
Days,
Weeks,
Months,
Years,
EveryLastDayOfMonth
}
public record PlannedPayment(
int Id,
decimal Amount,
string Name,
DateTime DateStart,
DateTime? DateEnd,
bool IsSubscription,
int? Reoccurences,
ReoccurenceType? ReoccurenceType)
{
public List<PlannedPayment> ExpandSubscription(DateTime startDate, DateTime endDate)
{
var expandedPayments = new List<PlannedPayment>();
if (!IsSubscription || Reoccurences is null || Reoccurences < 1 || ReoccurenceType is null)
return DateStart >= startDate && DateStart <= endDate ? [this] : [];
var occurrence = DateStart;
while (occurrence <= endDate && (DateEnd == null || occurrence <= DateEnd))
{
if (occurrence >= startDate)
{
expandedPayments.Add(new PlannedPayment(
0, // New ID not assigned
Amount,
Name,
occurrence,
null,
false,
null,
null
));
}
occurrence = GetNextOccurence(occurrence, Reoccurences.Value, ReoccurenceType.Value);
}
return expandedPayments;
DateTime GetNextOccurence(DateTime dateStart, int reoccurences, ReoccurenceType reoccurenceType)
{
switch (reoccurenceType)
{
case Core.ReoccurenceType.Days:
return dateStart.AddDays(reoccurences);
case Core.ReoccurenceType.Weeks:
return dateStart.AddDays(reoccurences * 7);
break;
case Core.ReoccurenceType.Months:
return dateStart.AddMonths(reoccurences);
break;
case Core.ReoccurenceType.Years:
return dateStart.AddYears(reoccurences);
break;
case Core.ReoccurenceType.EveryLastDayOfMonth:
var result = new DateTime(startDate.Year, startDate.Month,
DateTime.DaysInMonth(dateStart.Year, dateStart.Month));
if (result.Date != dateStart.Date) return result;
result = result.AddDays(1);
return new DateTime(result.Year, result.Month, DateTime.DaysInMonth(result.Year, result.Month));
default:
throw new ArgumentOutOfRangeException(nameof(reoccurenceType), reoccurenceType, null);
}
}
}
}

View File

@@ -0,0 +1,6 @@
namespace WheresMyMoney.Maui.Core;
// All the code in this file is only included on Android.
public class PlatformClass1
{
}

View File

@@ -0,0 +1,6 @@
namespace WheresMyMoney.Maui.Core;
// All the code in this file is only included on Mac Catalyst.
public class PlatformClass1
{
}

View File

@@ -0,0 +1,9 @@
using System;
namespace WheresMyMoney.Maui.Core
{
// All the code in this file is only included on Tizen.
public class PlatformClass1
{
}
}

View File

@@ -0,0 +1,6 @@
namespace WheresMyMoney.Maui.Core;
// All the code in this file is only included on Windows.
public class PlatformClass1
{
}

View File

@@ -0,0 +1,6 @@
namespace WheresMyMoney.Maui.Core;
// All the code in this file is only included on iOS.
public class PlatformClass1
{
}

View File

@@ -0,0 +1,253 @@
using Microsoft.Data.Sqlite;
namespace WheresMyMoney.Maui.Core;
public class Repository
{
private static Repository? _instance;
public static Repository Instance
{
get
{
if (_instance is not null) return _instance;
string dbPath = Path.Combine(FileSystem.AppDataDirectory, "wheresmymoney.db");
_instance = new Repository(new SqliteConnection($"Data Source={dbPath};"));
return _instance;
}
}
private readonly SqliteConnection _connection;
private Repository(SqliteConnection connection)
{
_connection = connection;
_connection.Open();
CreateTables();
}
~Repository()
{
_connection.Close();
}
public void CreateTables()
{
using var command = _connection.CreateCommand();
command.CommandText = """
CREATE TABLE IF NOT EXISTS planned_payment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount INTEGER NOT NULL,
name TEXT NOT NULL,
date_start TEXT NOT NULL,
date_end TEXT NULL,
is_subscription BOOLEAN NOT NULL,
reoccurences INTEGER NULL,
reoccurence_type INTEGER NULL CHECK(CASE reoccurences IS NOT NULL AND reoccurence_type IS NOT NULL WHEN TRUE THEN is_subscription ELSE NOT is_subscription END)
);
CREATE TABLE IF NOT EXISTS balance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount INTEGER NOT NULL,
date TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payday TINYINT NOT NULL CHECK(payday BETWEEN 1 AND 31)
);
""";
command.ExecuteNonQuery();
}
public void InsertPlannedPayment(decimal amount, string name, DateTime dateStart, DateTime? dateEnd,
bool isSubscription,
int? reoccurences, ReoccurenceType? reoccurenceType)
{
using var command = _connection.CreateCommand();
command.CommandText = """
INSERT INTO planned_payment (amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type)
VALUES (@amount, @name, @date_start, @date_end, @is_subscription, @reoccurences, @reoccurence_type);
""";
command.Parameters.AddWithValue("@amount", (int)(amount * 100));
command.Parameters.AddWithValue("@name", name);
command.Parameters.AddWithValue("@date_start", dateStart);
command.Parameters.AddWithValue("@date_end", dateEnd.HasValue ? dateEnd.Value : DBNull.Value);
command.Parameters.AddWithValue("@is_subscription", isSubscription);
command.Parameters.AddWithValue("@reoccurences", reoccurences is null ? DBNull.Value : reoccurences.Value);
command.Parameters.AddWithValue("@reoccurence_type",
reoccurenceType is null ? DBNull.Value : (int)reoccurenceType.Value);
command.ExecuteNonQuery();
DataChanged(this, EventArgs.Empty);
}
public void InsertBalance(decimal amount, DateTime date)
{
using var command = _connection.CreateCommand();
command.CommandText = """
INSERT INTO balance (amount, date)
VALUES (@amount, @date);
""";
command.Parameters.AddWithValue("@amount", (int)(amount * 100));
command.Parameters.AddWithValue("@date", date);
command.ExecuteNonQuery();
DataChanged(this, EventArgs.Empty);
}
public decimal GetNewestBalance()
{
using var command = _connection.CreateCommand();
command.CommandText = @"SELECT amount FROM balance ORDER BY date DESC LIMIT 1;";
var result = command.ExecuteScalar();
return result != null ? Convert.ToDecimal(result) / 100m : 0m;
}
public List<Balance> GetAllBalances()
{
var balances = new List<Balance>();
using var command = _connection.CreateCommand();
command.CommandText = @"SELECT id, amount, date FROM balance ORDER BY date;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
balances.Add(new Balance(
reader.GetInt32(0),
reader.GetDecimal(1) / 100m,
reader.GetDateTime(2)
));
}
return balances;
}
public byte GetPayday()
{
using var command = _connection.CreateCommand();
command.CommandText = "SELECT payday FROM settings LIMIT 1;";
var result = command.ExecuteScalar();
return result != null ? Convert.ToByte(result) : (byte)10;
}
public void ReplacePayday(byte newPayday)
{
using var command = _connection.CreateCommand();
command.CommandText = """
INSERT INTO settings (id, payday)
VALUES (1, @payday)
ON CONFLICT(id) DO UPDATE SET payday = excluded.payday;
""";
command.Parameters.AddWithValue("@payday", newPayday);
command.ExecuteNonQuery();
DataChanged(this, EventArgs.Empty);
}
public List<PlannedPayment> GetAllPlannedPayments()
{
var payments = new List<PlannedPayment>();
using var command = _connection.CreateCommand();
command.CommandText =
"SELECT id, amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type FROM planned_payment ORDER BY DATETIME(date_start);";
using var reader = command.ExecuteReader();
while (reader.Read())
{
payments.Add(new PlannedPayment(
reader.GetInt32(0),
reader.GetDecimal(1) / 100m,
reader.GetString(2),
reader.GetDateTime(3),
reader.IsDBNull(4) ? null : reader.GetDateTime(4),
reader.GetBoolean(5),
reader.IsDBNull(6) ? null : reader.GetInt32(6),
reader.IsDBNull(7) ? null : (ReoccurenceType)reader.GetInt32(7)
));
}
return payments;
}
public List<PlannedPayment> GetFilteredPlannedPayments()
{
var filteredPayments = new List<PlannedPayment>();
var now = DateTime.Now;
var nearestPayday = GetNearestPayday(now);
using var command = _connection.CreateCommand();
command.CommandText = """
SELECT id, amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type
FROM planned_payment
WHERE (
(is_subscription = 0 AND DATETIME(date_start) >= DATETIME(@currentDate) AND DATETIME(date_start) <= DATETIME(@nearestPayday)) OR
(is_subscription = 1 AND (date_end IS NULL OR DATETIME(date_end) >= DATETIME(@currentDate)) AND DATETIME(date_start) <= DATETIME(@nearestPayday))
)
ORDER BY DATETIME(date_start);
""";
command.Parameters.AddWithValue("@currentDate", now);
command.Parameters.AddWithValue("@nearestPayday", nearestPayday);
using var reader = command.ExecuteReader();
while (reader.Read())
{
filteredPayments.Add(new PlannedPayment(
reader.GetInt32(0),
reader.GetDecimal(1) / 100m,
reader.GetString(2),
reader.GetDateTime(3),
reader.IsDBNull(4) ? null : reader.GetDateTime(4),
reader.GetBoolean(5),
reader.IsDBNull(6) ? null : reader.GetInt32(6),
reader.IsDBNull(7) ? null : (ReoccurenceType)reader.GetInt32(7)
));
}
return filteredPayments;
}
public DateTime GetNearestPayday(DateTime now)
{
var payday = GetPayday();
var nearestPayday =
new DateOnly(now.Year, now.Month,
payday > DateTime.DaysInMonth(now.Year, now.Month)
? DateTime.DaysInMonth(now.Year, now.Month)
: payday)
.ToDateTime(TimeOnly.MinValue);
if (now.Day <= payday) return nearestPayday;
var newDate = new DateOnly(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month)).AddDays(1);
nearestPayday =
new DateOnly(newDate.Year, newDate.Month,
payday > DateTime.DaysInMonth(newDate.Year, newDate.Month)
? DateTime.DaysInMonth(newDate.Year, newDate.Month)
: payday).ToDateTime(TimeOnly.MinValue);
return nearestPayday;
}
public void RemovePlannedPayment(int id)
{
using var command = _connection.CreateCommand();
command.CommandText = """
DELETE FROM planned_payment
WHERE id = @id;
""";
command.Parameters.AddWithValue("@id", id);
command.ExecuteNonQuery();
DataChanged(this, EventArgs.Empty);
}
public event EventHandler DataChanged;
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.1" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)"/>
</ItemGroup>
</Project>