Can now schedule events

This commit is contained in:
2026-01-08 23:19:30 -06:00
parent 6b665750e3
commit a2b948beb7
10 changed files with 351 additions and 19 deletions

View File

@@ -0,0 +1,30 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CritterFolio.Controls.CritterEventButton">
<Grid HorizontalAlignment="Stretch" Height="50"
Background="{DynamicResource AccentBackground}"
x:Name="MainGrid"
ColumnDefinitions="Auto, *, Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Label FontFamily="{DynamicResource Phosphor}"
Foreground="{DynamicResource PrimaryForeground}"
VerticalAlignment="Center"
Content="&#xe7b2;"
FontSize="25"/>
<Label x:Name="NameLabel"
Margin="10, 0"
Content="Event Name/Date"
Foreground="{DynamicResource PrimaryForeground}"
VerticalAlignment="Center"
FontSize="20" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button x:Name="ViewButton" Classes="icon" Content="&#xe220;" />
<Button x:Name="DeleteButton" Classes="icon" Content="&#xe4a6;" Foreground="Red" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,53 @@
using System;
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using CritterFolio.DataModels;
using CritterFolio.Pages;
using CritterFolio.Services;
namespace CritterFolio.Controls;
public partial class CritterEventButton : UserControl
{
private CritterEvent? _critterEvent;
public event EventHandler? Deleted;
public CritterEventButton()
{
InitializeComponent();
}
public void Init(CritterEvent critterEvent)
{
_critterEvent = critterEvent;
NameLabel.Content = critterEvent.Name;
ViewButton.Click += ViewButtonOnClick;
DeleteButton.Click += DeleteButtonOnClick;
}
private async void DeleteButtonOnClick(object? sender, RoutedEventArgs e)
{
if (_critterEvent is null) return;
var result = await DialogHelper.ShowConfirmationDialog("Are you sure you want to delete the event?");
if (!result) return;
await DatabaseService.DeleteEvent(_critterEvent);
Deleted?.Invoke(this, EventArgs.Empty);
}
private async void ViewButtonOnClick(object? sender, RoutedEventArgs e)
{
if (_critterEvent is null) return;
var page = new EventPage();
page.Init(_critterEvent);
Sys.Navigation?.PushPage(page);
}
}

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="720" d:DesignHeight="1280" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="500"
x:Class="CritterFolio.Controls.DocumentItem"> x:Class="CritterFolio.Controls.DocumentItem">
<Grid HorizontalAlignment="Stretch" Height="50" <Grid HorizontalAlignment="Stretch" Height="50"
Background="{DynamicResource AccentBackground}" Background="{DynamicResource AccentBackground}"
@@ -20,9 +20,7 @@
Content="Document" Content="Document"
Foreground="{DynamicResource PrimaryForeground}" Foreground="{DynamicResource PrimaryForeground}"
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="20" FontSize="20" />
MaxWidth="150"
/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal"> <StackPanel Grid.Column="2" Orientation="Horizontal">
<Button x:Name="ViewButton" Classes="icon" Content="&#xe220;" /> <Button x:Name="ViewButton" Classes="icon" Content="&#xe220;" />

View File

@@ -0,0 +1,15 @@
using System;
using SQLite;
namespace CritterFolio.DataModels;
[Table("Events")]
public class CritterEvent
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int CritterId { get; set; }
public string Name { get; set; } = "New Event";
public DateTime DateTime { get; set; } = DateTime.Now;
public string? Description { get; set; } = "New Event";
}

View File

@@ -39,12 +39,19 @@
<TextBox x:Name="NotesBox" MinHeight="100" AcceptsReturn="True" TextWrapping="Wrap" /> <TextBox x:Name="NotesBox" MinHeight="100" AcceptsReturn="True" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<Grid ColumnDefinitions="Auto, *, Auto" Margin="0, 10">
<Label Grid.Column="0" Classes="heading1">Events</Label>
<Button x:Name="AddEventButton" Grid.Column="2">Add New</Button>
</Grid>
<StackPanel x:Name="EventsStack" Spacing="5" HorizontalAlignment="Stretch" Margin="20, 5"/>
<Grid ColumnDefinitions="Auto, *, Auto" Margin="0, 10"> <Grid ColumnDefinitions="Auto, *, Auto" Margin="0, 10">
<Label Grid.Column="0" Classes="heading1">Documents</Label> <Label Grid.Column="0" Classes="heading1">Documents</Label>
<Button x:Name="AddDocButton" Grid.Column="2">Add New</Button> <Button x:Name="AddDocButton" Grid.Column="2">Add New</Button>
</Grid> </Grid>
<StackPanel x:Name="DocsStack" HorizontalAlignment="Stretch" Margin="20, 5"/> <StackPanel x:Name="DocsStack" Spacing="5" HorizontalAlignment="Stretch" Margin="20, 5"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -20,6 +20,7 @@ public partial class CritterPage : Page
private Critter? _critter; private Critter? _critter;
private List<CritterComboBoxDisplay> _critterDisplays = []; private List<CritterComboBoxDisplay> _critterDisplays = [];
private List<Document> _documents = []; private List<Document> _documents = [];
private List<CritterEvent> _critterEvents = [];
public CritterPage() public CritterPage()
{ {
@@ -30,20 +31,36 @@ public partial class CritterPage : Page
{ {
_critter = critter; _critter = critter;
PfpButton.Click += OpenFileButton_Clicked;
GenderOption.ItemsSource = Enum.GetNames(typeof(Gender)); GenderOption.ItemsSource = Enum.GetNames(typeof(Gender));
AddDocButton.Click += AddDocButtonOnClick;
await UpdateInfo(); await UpdateInfo();
} }
public override void Refresh() public override void Refresh()
{ {
base.Refresh(); base.Refresh();
Sys.Navigation?.SetTitle(_critter?.Name ?? "Error");
CreateHeaderButtons(); CreateHeaderButtons();
AddConnections();
RegenEventsList();
RegenDocsList();
}
private void AddConnections()
{
PfpButton.Click += OpenFileButton_Clicked;
AddDocButton.Click += AddDocButtonOnClick;
AddEventButton.Click += AddEventButtonOnClick;
}
protected override void ClearConnections()
{
PfpButton.Click -= OpenFileButton_Clicked;
AddDocButton.Click -= AddDocButtonOnClick;
AddEventButton.Click -= AddEventButtonOnClick;
} }
private async Task LoadComboItemsAsync() private async Task LoadComboItemsAsync()
@@ -133,10 +150,40 @@ public partial class CritterPage : Page
} }
RegenDocsList(); RegenDocsList();
RegenEventsList();
}
private async void RegenEventsList()
{
if (_critter is null) return;
_critterEvents.Clear();
var events = await DatabaseService.GetAllEventsForCritter(_critter.Id);
_critterEvents.AddRange(events);
EventsStack.Children.Clear();
foreach (var critterEvent in _critterEvents)
{
CreateCritterEventItem(critterEvent);
}
}
private void CreateCritterEventItem(CritterEvent critterEvent)
{
var item = new CritterEventButton();
item.Init(critterEvent);
EventsStack.Children.Add(item);
item.Deleted += async (sender, args) =>
{
RegenEventsList();
};
} }
private async void RegenDocsList() private async void RegenDocsList()
{ {
if (_critter is null) return;
_documents.Clear(); _documents.Clear();
var docs = await DatabaseService.GetAllDocumentsForCritter(_critter.Id); var docs = await DatabaseService.GetAllDocumentsForCritter(_critter.Id);
_documents.AddRange(docs); _documents.AddRange(docs);
@@ -376,4 +423,18 @@ public partial class CritterPage : Page
CreateDocItem(doc); CreateDocItem(doc);
} }
private async void AddEventButtonOnClick(object? sender, RoutedEventArgs e)
{
if (_critter is null) return;
var newEvent = new CritterEvent()
{
CritterId = _critter.Id
};
await DatabaseService.AddEvent(newEvent);
var page = new EventPage();
page.Init(newEvent);
Sys.Navigation?.PushPage(page);
}
} }

View File

@@ -0,0 +1,17 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CritterFolio.Pages.EventPage">
<StackPanel>
<Label>Name:</Label>
<TextBox x:Name="NameBox" Watermark="Name" />
<Label>Date:</Label>
<CalendarDatePicker x:Name="DatePicker" />
<Label>Time:</Label>
<TimePicker x:Name="TimePicker" />
<Label>Description:</Label>
<TextBox x:Name="DescriptionBox" Height="500" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,112 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using CritterFolio.DataModels;
using CritterFolio.Services;
namespace CritterFolio.Pages;
public partial class EventPage : Page
{
private CritterEvent? _critterEvent;
public EventPage()
{
InitializeComponent();
}
public void Init(CritterEvent critterEvent)
{
_critterEvent = critterEvent;
}
public override void Refresh()
{
base.Refresh();
UpdateInfo();
var saveBttn = new Button()
{
Classes = { "headerBttn" },
Content = "\ue248"
};
saveBttn.Click += SaveButtonClicked;
Sys.Navigation?.AddHeaderButton(saveBttn);
var delBttn = new Button()
{
Classes = { "headerBttn" },
Content = "\ue4a6"
};
delBttn.Foreground = new SolidColorBrush(Colors.Red);
delBttn.Click += DeleteButtonClicked;
Sys.Navigation?.AddHeaderButton(delBttn);
}
private void UpdateInfo()
{
Sys.Navigation?.SetTitle(_critterEvent?.Name ?? "New Event");
NameBox.Text = _critterEvent?.Name;
DatePicker.SelectedDate = _critterEvent?.DateTime.Date;
TimePicker.SelectedTime = _critterEvent?.DateTime.ToLocalTime().TimeOfDay;
DescriptionBox.Text = _critterEvent?.Description;
}
private async void DeleteButtonClicked(object? sender, RoutedEventArgs e)
{
if (_critterEvent is null) return;
var result = await DialogHelper.ShowConfirmationDialog("Are you sure you wantt to delete this event?");
if (!result) return;
await DatabaseService.DeleteEvent(_critterEvent);
Sys.Navigation?.PopPage();
}
private async void SaveButtonClicked(object? sender, RoutedEventArgs e)
{
if (_critterEvent is null) return;
var result = await DialogHelper.ShowConfirmationDialog("Save this event?");
if (!result) return;
var validationResult = await ValidateAndSave();
if (!validationResult.isValid)
{
await DialogHelper.ShowMessage($"Could not save event:\n{validationResult.errString}");
return;
}
await DatabaseService.UpdateEvent(_critterEvent);
}
private async Task<(bool isValid, string errString)> ValidateAndSave()
{
if (_critterEvent is null) return (false, "Critter was null");
var err = "";
if (string.IsNullOrEmpty(NameBox.Text))
{
err += "Name is required\n";
}
else
{
_critterEvent.Name = NameBox.Text;
}
_critterEvent.DateTime = new DateTime(DateOnly.FromDateTime(DatePicker.DisplayDate), TimeOnly.FromTimeSpan((TimeSpan)TimePicker.SelectedTime!));
_critterEvent.Description = DescriptionBox.Text;
return string.IsNullOrEmpty(err) ? (true, err) : (false, err);
}
}

View File

@@ -24,7 +24,7 @@ public static class DatabaseService
try try
{ {
await _db.CreateTablesAsync<Critter, Document>(); await _db.CreateTablesAsync<Critter, Document, CritterEvent>();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -37,6 +37,7 @@ public static class DatabaseService
await Init(); await Init();
await _db?.DeleteAllAsync<Critter>()!; await _db?.DeleteAllAsync<Critter>()!;
await _db?.DeleteAllAsync<Document>()!; await _db?.DeleteAllAsync<Document>()!;
await _db?.DeleteAllAsync<CritterEvent>()!;
} }
#region Critter operations #region Critter operations
@@ -137,4 +138,49 @@ public static class DatabaseService
} }
#endregion #endregion
#region CritterEvent operations
public static async Task<bool> AddEvent(CritterEvent critterEvent)
{
await Init();
var result = await _db?.InsertAsync(critterEvent)!;
return !(result <= 0);
}
public static async Task<bool> UpdateEvent(CritterEvent critterEvent)
{
await Init();
return await _db?.UpdateAsync(critterEvent)! != 0;
}
public static async Task<bool> DeleteEvent(CritterEvent critterEvent)
{
await Init();
return await _db?.DeleteAsync(critterEvent)! != 0;
}
public static async Task<bool> DeleteEvent(int id)
{
await Init();
return await _db?.DeleteAsync<CritterEvent>(id)! != 0;
}
public static async Task<List<CritterEvent>> GetAllEvents()
{
await Init();
var result = await _db?.Table<CritterEvent>().ToListAsync()!;
return result ?? [];
}
public static async Task<List<CritterEvent>> GetAllEventsForCritter(int critterId)
{
await Init();
var result = await _db?.Table<CritterEvent>()
.Where(ce => ce.CritterId == critterId)
.ToListAsync()!;
return result ?? [];
}
#endregion
} }

View File

@@ -16,14 +16,7 @@ public class Tests
[Test] [Test]
public async Task TestAddingCritter() public async Task TestAddingCritter()
{ {
var homePage = new HomePage(); Assert.That(await DatabaseService.AddCritter(new Critter(){Name = "Test Critter"}), Is.True);
var currentCritterDbCount = (await DatabaseService.GetAllCritters()).Count;
homePage.AddButtonClicked(null, new RoutedEventArgs());
var newCritterDbCount = (await DatabaseService.GetAllCritters()).Count;
Assert.That(newCritterDbCount == currentCritterDbCount + 1, Is.True);
} }
[Test] [Test]