Adding final project to dev branch

This commit is contained in:
2025-12-19 18:53:21 -06:00
commit fd2ab3e14c
62 changed files with 4730 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Views.AssessmentPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Save"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe248;', FontFamily='Phosphor', Size=20, Color=AliceBlue}"
Clicked="SaveButton_OnClicked"/>
<ToolbarItem Text="Delete"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe4a6;', FontFamily='Phosphor', Size=20, Color=Red}"
Clicked="DeleteButton_OnClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid RowDefinitions="*">
<ScrollView HorizontalOptions="Fill"
HorizontalScrollBarVisibility="Never">
<VerticalStackLayout HorizontalOptions="Fill">
<Label Text="Assessment Info"
FontAttributes="Bold"
FontSize="20"
Margin="20, 10, 20, 5"/>
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Title:"
VerticalOptions="Center" />
<Entry x:Name="TitleEntry"
Grid.Column="1"
Placeholder="Enter value here..."
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Start date:"
VerticalOptions="Center" />
<DatePicker x:Name="StartDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Expected end date:"
VerticalOptions="Center" />
<DatePicker x:Name="EndDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Label Text="Notifications"
FontAttributes="Bold"
FontSize="20"
Margin="20, 5"/>
<Grid ColumnDefinitions="Auto, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Start Date:"
VerticalOptions="Center" />
<CheckBox x:Name="StartNotifCheck"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="Auto, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="End Date:"
VerticalOptions="Center" />
<CheckBox x:Name="EndNotifCheck"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
</VerticalStackLayout>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WguApp.Models;
using WguApp.Services;
namespace WguApp.Views;
public partial class AssessmentPage : ContentPage
{
private Assessment _assessment;
public AssessmentPage(Assessment assessment)
{
InitializeComponent();
_assessment = assessment;
Title = assessment.Name;
TitleEntry.Text = _assessment.Name;
StartDatePicker.Date = _assessment.StartDate;
EndDatePicker.Date = _assessment.EndDate;
StartNotifCheck.IsChecked = _assessment.StartNotifCheck;
EndNotifCheck.IsChecked = _assessment.EndNotifCheck;
StartNotifCheck.CheckedChanged += async (sender, e) => { await ToggleStartNotification(); };
EndNotifCheck.CheckedChanged += async (sender, e) => { await ToggleEndNotification(); };
}
private async Task ToggleStartNotification()
{
if (StartNotifCheck.IsChecked)
{
var id = await LocalNotificationService.ScheduleNotification($"{_assessment.Name} Start Reminder", $"Your assessment '{_assessment.Id} starts in 12 hours'", _assessment.StartDate.Subtract(TimeSpan.FromHours(12)));
_assessment.StartNotifId = id ?? 0;
LoggerService.LogToFile($"{_assessment.Id}:{_assessment.Name} set start notification {_assessment.StartNotifId}");
}
else
{
var res = await LocalNotificationService.CancelNotification(_assessment.StartNotifId);
LoggerService.LogToFile($"{_assessment.Id}:{_assessment.Name} canceled start notification {_assessment.StartNotifId}");
_assessment.StartNotifId = 0;
}
_assessment.StartNotifCheck = StartNotifCheck.IsChecked;
}
private async Task ToggleEndNotification()
{
if (EndNotifCheck.IsChecked)
{
var id = await LocalNotificationService.ScheduleNotification($"{_assessment.Name} End Reminder", $"Your assessment '{_assessment.Id} ends in 12 hours'", _assessment.EndDate.Subtract(TimeSpan.FromHours(12)));
_assessment.EndNotifId = id ?? 0;
LoggerService.LogToFile($"{_assessment.Id}:{_assessment.Name} set end notification {_assessment.EndNotifId}");
}
else
{
var res = await LocalNotificationService.CancelNotification(_assessment.EndNotifId);
LoggerService.LogToFile($"{_assessment.Id}:{_assessment.Name} canceled end notification {_assessment.EndNotifId}");
_assessment.EndNotifId = 0;
}
_assessment.EndNotifCheck = EndNotifCheck.IsChecked;
}
private (string msg, bool result) ValidateFields()
{
var message = string.Empty;
if (string.IsNullOrWhiteSpace(TitleEntry.Text)) message += "Title cannot be blank\n";
if (StartDatePicker.Date > EndDatePicker.Date) message += "Start Date cannot be after the End Date\n";
return string.IsNullOrWhiteSpace(message) ? (message, true) : (message, false);
}
private async Task<bool> Save()
{
var validationResult = ValidateFields();
if (!validationResult.result)
{
await DisplayAlert("Validation Error", validationResult.msg, "Ok");
return false;
}
_assessment.Name = TitleEntry.Text;
_assessment.StartDate = StartDatePicker.Date;
_assessment.EndDate = EndDatePicker.Date;
_assessment.StartNotifCheck = StartNotifCheck.IsChecked;
_assessment.EndNotifCheck = EndNotifCheck.IsChecked;
var updateResult = await DatabaseService.UpdateAssessment(_assessment);
if (updateResult)
{
await DisplayAlert("Info", $"{_assessment.Name} Saved!", "Ok");
}
else
{
await DisplayAlert("Error", "Could not save term", "Ok");
}
Title = _assessment.Name;
if (_assessment.StartNotifId != 0)
{
await LocalNotificationService.UpdateNotification(_assessment.StartNotifId, $"{_assessment.Name} Start Reminder",
$"Your course '{_assessment.Id} starts in 12 hours'", _assessment.StartDate.Subtract(TimeSpan.FromHours(12)));
}
if (_assessment.EndNotifId != 0)
{
await LocalNotificationService.UpdateNotification(_assessment.EndNotifId, $"{_assessment.Name} End Reminder",
$"Your course '{_assessment.Id} ends in 12 hours'", _assessment.EndDate.Subtract(TimeSpan.FromHours(12)));
}
return true;
}
private async void SaveButton_OnClicked(object? sender, EventArgs e)
{
await Save();
}
private async void DeleteButton_OnClicked(object? sender, EventArgs e)
{
var result = await DisplayAlert("Delete Course", $"Do you really want to delete '{_assessment.Name}'? This action cannot be undone.", "Delete", "Cancel");
if (!result) return;
await DatabaseService.DeleteAssessment(_assessment);
await Navigation.PopAsync();
}
}

View File

@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Views.CoursePage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Save"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe248;', FontFamily='Phosphor', Size=20, Color=AliceBlue}"
Clicked="SaveButton_OnClicked"/>
<ToolbarItem Text="Delete"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe4a6;', FontFamily='Phosphor', Size=20, Color=Red}"
Clicked="DeleteButton_OnClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid RowDefinitions="*">
<ScrollView HorizontalOptions="Fill"
HorizontalScrollBarVisibility="Never">
<VerticalStackLayout HorizontalOptions="Fill">
<Label Text="Course Info"
FontAttributes="Bold"
FontSize="20"
Margin="20, 10, 20, 5"/>
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Title:"
VerticalOptions="Center" />
<Entry x:Name="TitleEntry"
Grid.Column="1"
Placeholder="Enter value here..."
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Start date:"
VerticalOptions="Center" />
<DatePicker x:Name="StartDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Expected end date:"
VerticalOptions="Center" />
<DatePicker x:Name="EndDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Status:"
VerticalOptions="Center" />
<Picker x:Name="StatusPicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Label Text="Instructor"
FontAttributes="Bold"
FontSize="20"
Margin="20, 10, 20, 5"/>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Name:"
VerticalOptions="Center" />
<Entry x:Name="InstructorNameEntry"
Grid.Column="1"
Placeholder="Name"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10, 20, 5">
<Label Grid.Column="0"
Text="Phone:"
VerticalOptions="Center" />
<Entry x:Name="InstructorPhoneEntry"
Grid.Column="1"
Placeholder="Phone"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Email:"
VerticalOptions="Center" />
<Entry x:Name="InstructorEmailEntry"
Grid.Column="1"
Placeholder="Email"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="*, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 5">
<Label Grid.Column="0" Text="Notes"
FontAttributes="Bold"
FontSize="20"
VerticalOptions="Center"/>
<Button Grid.Column="1"
Text="&#xe406;"
FontFamily="Phosphor"
FontSize="20"
WidthRequest="20"
HeightRequest="20"
TextColor="AliceBlue"
BackgroundColor="Transparent"
HorizontalOptions="End"
VerticalOptions="Center"
Clicked="ShareNotesButton_OnClicked"/>
</Grid>
<Border HorizontalOptions="Fill"
Margin="30, 5"
Stroke="AliceBlue"
StrokeThickness="2">
<Editor x:Name="NotesEditor"
HeightRequest="150"/>
</Border>
<Label Text="Notifications"
FontAttributes="Bold"
FontSize="20"
Margin="20, 5"/>
<Grid ColumnDefinitions="Auto, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Start Date:"
VerticalOptions="Center" />
<CheckBox x:Name="StartNotifCheck"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="Auto, Auto"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="End Date:"
VerticalOptions="Center" />
<CheckBox x:Name="EndNotifCheck"
Grid.Column="1"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
<Grid RowDefinitions="*, Auto"
ColumnDefinitions="*, Auto"
HorizontalOptions="Fill"
HeightRequest="40"
Margin="30, 20, 30, 0">
<Label Text="Assessments"
Grid.Row="0"
Grid.Column="0"
FontSize="15"
VerticalOptions="Center"
HorizontalOptions="Fill"
HorizontalTextAlignment="Start"
HeightRequest="20" />
<Button Grid.Row="0"
Grid.Column="1"
Text="&#xe3d4;"
FontFamily="Phosphor"
FontSize="15"
WidthRequest="20"
HeightRequest="20"
TextColor="AliceBlue"
BackgroundColor="Transparent"
HorizontalOptions="End"
VerticalOptions="Center"
Clicked="AddAssessmentButton_OnClicked"/>
</Grid>
<BoxView
Color="AliceBlue"
HeightRequest="2"
HorizontalOptions="Fill"
Margin="30, 0, 30, 20" />
<VerticalStackLayout x:Name="AssessmentButtonStack"
Margin="30,10"
HorizontalOptions="Fill"
Spacing="5"/>
</VerticalStackLayout>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WguApp.Controls;
using WguApp.Models;
using WguApp.Services;
namespace WguApp.Views;
public partial class CoursePage : ContentPage
{
private Course _course;
private Assessment? _oa = null;
private Assessment? _pa = null;
public CoursePage(Course course)
{
InitializeComponent();
_course = course;
Title = _course.Name;
StatusPicker.ItemsSource = Enum.GetValues<CourseStatus>();
TitleEntry.Text = _course.Name;
StartDatePicker.Date = _course.StartDate;
EndDatePicker.Date = _course.EndDate;
StatusPicker.SelectedItem = _course.Status;
InstructorNameEntry.Text = _course.InstructorName;
InstructorEmailEntry.Text = _course.InstructorEmail;
InstructorPhoneEntry.Text = _course.InstructorPhone;
NotesEditor.Text = _course.Notes;
StartNotifCheck.IsChecked = _course.StartNotifCheck;
EndNotifCheck.IsChecked = _course.EndNotifCheck;
StartNotifCheck.CheckedChanged += async (sender, e) => { await ToggleStartNotification(); };
EndNotifCheck.CheckedChanged += async (sender, e) => { await ToggleEndNotification(); };
}
private async Task ToggleStartNotification()
{
if (StartNotifCheck.IsChecked)
{
var id = await LocalNotificationService.ScheduleNotification($"{_course.Name} Start Reminder", $"Your course '{_course.Id} starts in 12 hours'", _course.StartDate.Subtract(TimeSpan.FromHours(12)));
_course.StartNotifId = id ?? 0;
LoggerService.LogToFile($"{_course.Id}:{_course.Name} set start notification {_course.StartNotifId}");
}
else
{
var res = await LocalNotificationService.CancelNotification(_course.StartNotifId);
LoggerService.LogToFile($"{_course.Id}:{_course.Name} canceled start notification {_course.StartNotifId}");
_course.StartNotifId = 0;
}
_course.StartNotifCheck = StartNotifCheck.IsChecked;
}
private async Task ToggleEndNotification()
{
if (EndNotifCheck.IsChecked)
{
var id = await LocalNotificationService.ScheduleNotification($"{_course.Name} End Reminder", $"Your course '{_course.Id} ends in 12 hours'", _course.EndDate.Subtract(TimeSpan.FromHours(12)));
_course.EndNotifId = id ?? 0;
LoggerService.LogToFile($"{_course.Id}:{_course.Name} set end notification {_course.EndNotifId}");
}
else
{
var res = await LocalNotificationService.CancelNotification(_course.EndNotifId);
LoggerService.LogToFile($"{_course.Id}:{_course.Name} canceled end notification {_course.EndNotifId}");
_course.EndNotifId = 0;
}
_course.EndNotifCheck = EndNotifCheck.IsChecked;
}
private (string msg, bool result) ValidateFields()
{
var message = string.Empty;
if (string.IsNullOrWhiteSpace(TitleEntry.Text)) message += "Title cannot be blank\n";
if (StartDatePicker.Date > EndDatePicker.Date) message += "Start Date cannot be after the End Date\n";
if (string.IsNullOrWhiteSpace(InstructorNameEntry.Text)) message += "Instructor Name cannot be blank\n";
if (string.IsNullOrWhiteSpace(InstructorPhoneEntry.Text)) message += "Instructor Phone cannot be blank\n";
if (string.IsNullOrWhiteSpace(InstructorEmailEntry.Text)) message += "Instructor Email cannot be blank\n";
return string.IsNullOrWhiteSpace(message) ? (message, true) : (message, false);
}
private async void SaveButton_OnClicked(object? sender, EventArgs e)
{
await Save();
}
private async Task<bool> Save()
{
var validationResult = ValidateFields();
if (!validationResult.result)
{
await DisplayAlert("Validation Error", validationResult.msg, "Ok");
return false;
}
_course.Name = TitleEntry.Text;
_course.StartDate = StartDatePicker.Date;
_course.EndDate = EndDatePicker.Date;
_course.InstructorName = InstructorNameEntry.Text;
_course.InstructorPhone = InstructorPhoneEntry.Text;
_course.InstructorEmail = InstructorEmailEntry.Text;
_course.Notes = NotesEditor.Text;
_course.StartNotifCheck = StartNotifCheck.IsChecked;
_course.EndNotifCheck = EndNotifCheck.IsChecked;
var updateResult = await DatabaseService.UpdateCourse(_course);
if (updateResult)
{
await DisplayAlert("Info", $"{_course.Name} Saved!", "Ok");
}
else
{
await DisplayAlert("Error", "Could not save term", "Ok");
}
Title = _course.Name;
if (_course.StartNotifId != 0)
{
await LocalNotificationService.UpdateNotification(_course.StartNotifId, $"{_course.Name} Start Reminder",
$"Your course '{_course.Id} starts in 12 hours'", _course.StartDate.Subtract(TimeSpan.FromHours(12)));
}
if (_course.EndNotifId != 0)
{
await LocalNotificationService.UpdateNotification(_course.EndNotifId, $"{_course.Name} End Reminder",
$"Your course '{_course.Id} ends in 12 hours'", _course.EndDate.Subtract(TimeSpan.FromHours(12)));
}
return true;
}
private async void DeleteButton_OnClicked(object? sender, EventArgs e)
{
var result = await DisplayAlert("Delete Course", $"Do you really want to delete '{_course.Name}'? This action cannot be undone.", "Delete", "Cancel");
if (!result) return;
await DatabaseService.DeleteCourse(_course);
await Navigation.PopAsync();
}
private async void ShareNotesButton_OnClicked(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(NotesEditor.Text))
{
await DisplayAlert("Info", "Failed to share: Notes are empty", "Ok");
}
await Share.Default.RequestAsync(new ShareTextRequest
{
Title = "Share course notes",
Text = $"-- Course notes for {_course.Name} --\n{NotesEditor.Text}"
});
}
private async Task RefreshAssessmentsList()
{
AssessmentButtonStack.Children.Clear();
var allAssessments = await DatabaseService.GetAssessmentsByCourseId(_course.Id);
_oa = allAssessments.FirstOrDefault(a => a.Type == AssessmentType.Objective);
_pa = allAssessments.FirstOrDefault(a => a.Type == AssessmentType.Performance);
if (_oa is not null)
{
var oaBtn = new CustomButton()
{
Text = _oa.Name,
DateText = $"{DateOnly.FromDateTime(_oa.StartDate).ToString()} - {DateOnly.FromDateTime(_oa.EndDate).ToString()}"
};
AssessmentButtonStack.Add(oaBtn);
oaBtn.Clicked += async (sender, e) => { await HandleAssessmentButtonClick(_oa); };
}
if (_pa is not null)
{
var paBtn = new CustomButton()
{
Text = _pa.Name,
DateText = $"{DateOnly.FromDateTime(_pa.StartDate).ToString()} - {DateOnly.FromDateTime(_pa.EndDate).ToString()}"
};
AssessmentButtonStack.Add(paBtn);
paBtn.Clicked += async (sender, e) => { await HandleAssessmentButtonClick(_pa); };
}
}
protected async override void OnNavigatedTo(NavigatedToEventArgs args)
{
await RefreshAssessmentsList();
}
private async Task HandleAssessmentButtonClick(Assessment assessment)
{
await Navigation.PushAsync(new AssessmentPage(assessment));
}
private async void AddAssessmentButton_OnClicked(object? sender, EventArgs e)
{
if (_oa is not null && _pa is not null)
{
await DisplayAlert("Info", "Cannot add any more assessments", "Ok");
return;
}
var action = await DisplayActionSheet("Which type of lesson do you want to add?", "Cancel", null, "Performance",
"Objective");
if (action is null or "Cancel") return;
var assessment = new Assessment
{
CourseId = _course.Id,
StartDate = DateTime.Now,
EndDate = DateTime.Today.AddDays(30),
StartNotifCheck = false,
EndNotifCheck = false
};
switch (action)
{
case "Objective":
if (_oa is not null)
{
await DisplayAlert("Info", "Cannot add any more objective assessments", "Ok");
break;
}
assessment.Type = AssessmentType.Objective;
assessment.Name = "Objective Assessment";
break;
case "Performance":
if (_pa is not null)
{
await DisplayAlert("Info", "Cannot add any more performance assessments", "Ok");
break;
}
assessment.Type = AssessmentType.Performance;
assessment.Name = "Performance Assessment";
break;
}
await DatabaseService.AddAssessment(assessment);
await RefreshAssessmentsList();
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Views.HomePage"
Title="Home">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add"
Order="Primary"
Priority="0"
Clicked="TestButton_OnClicked"
IconImageSource="{FontImageSource Glyph='&#xe79e;', FontFamily='Phosphor', Size=24, Color=AliceBlue}" />
</ContentPage.ToolbarItems>
<Grid HorizontalOptions="Fill"
Padding="30, 20"
RowDefinitions="Auto, Auto, *, Auto">
<Grid RowDefinitions="*, Auto"
ColumnDefinitions="*, Auto"
HorizontalOptions="Fill"
HeightRequest="40"
Grid.Row="0">
<Label Text="Terms"
Grid.Row="0"
Grid.Column="0"
FontSize="15"
VerticalOptions="Center"
HorizontalOptions="Fill"
HorizontalTextAlignment="Start"
HeightRequest="20" />
<Button Grid.Row="0"
Grid.Column="1"
Text="&#xe3d4;"
FontFamily="Phosphor"
FontSize="15"
WidthRequest="20"
HeightRequest="20"
TextColor="AliceBlue"
BackgroundColor="Transparent"
HorizontalOptions="End"
VerticalOptions="Center"
Clicked="AddTermButton_OnClicked"/>
</Grid>
<BoxView
Grid.Row="1"
Color="AliceBlue"
HeightRequest="2"
HorizontalOptions="Fill"
Margin="0, 0, 0, 20" />
<ScrollView HorizontalOptions="Fill"
HorizontalScrollBarVisibility="Never"
Grid.Row="2">
<VerticalStackLayout x:Name="TermButtonStack"
Margin="0,10"
HorizontalOptions="Fill"
Spacing="5"/>
</ScrollView>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WguApp.Controls;
using WguApp.Models;
using WguApp.Services;
namespace WguApp.Views;
public partial class HomePage : ContentPage
{
private List<Term> _terms = [];
public HomePage()
{
InitializeComponent();
_ = RefreshTermList();
}
private async Task RefreshTermList()
{
_terms.Clear();
_terms = await DatabaseService.GetAllTerms();
TermButtonStack.Children.Clear();
foreach (var term in _terms)
{
var btn = new CustomButton()
{
Text = term.Name,
DateText = $"{DateOnly.FromDateTime(term.StartDate).ToString()} - {DateOnly.FromDateTime(term.EndDate).ToString()}"
};
TermButtonStack.Add(btn);
btn.Clicked += (sender, e) =>
{
HandleTermButtonClick(term);
};
}
}
private async void AddTermButton_OnClicked(object? sender, EventArgs e)
{
var newTerm = new Term()
{
Name = "New Term",
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30)
};
await DatabaseService.AddTerm(newTerm);
await RefreshTermList();
}
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
await RefreshTermList();
}
private void HandleTermButtonClick(Term term)
{
Navigation.PushAsync(new TermPage(term));
}
private void TestButton_OnClicked(object? sender, EventArgs e)
{
Navigation.PushAsync(new TestPage());
}
}

134
WguApp/Views/TermPage.xaml Normal file
View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Views.TermPage"
Title="Term">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Save"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe248;', FontFamily='Phosphor', Size=20, Color=AliceBlue}"
Clicked="SaveButton_OnClicked"/>
<ToolbarItem Text="Delete"
Order="Primary"
Priority="0"
IconImageSource="{FontImageSource Glyph='&#xe4a6;', FontFamily='Phosphor', Size=20, Color=Red}"
Clicked="DeleteButton_OnClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid RowDefinitions="*">
<ScrollView HorizontalOptions="Fill"
HorizontalScrollBarVisibility="Never">
<VerticalStackLayout HorizontalOptions="Fill">
<Label Text="Term Info"
FontAttributes="Bold"
FontSize="20"
Margin="20, 10, 20, 5"/>
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Title:"
VerticalOptions="Center"
FontAttributes="Bold" />
<Entry x:Name="TitleEntry"
Grid.Column="1"
Placeholder="Enter value here..."
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Start date:"
VerticalOptions="Center"
FontAttributes="Bold" />
<DatePicker x:Name="StartDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
ColumnSpacing="10"
Padding="5"
HorizontalOptions="Fill"
Margin="20, 10">
<Label Grid.Column="0"
Text="Expected end date:"
VerticalOptions="Center"
FontAttributes="Bold" />
<DatePicker x:Name="EndDatePicker"
Grid.Column="1"
VerticalOptions="Center"
WidthRequest="200"
HorizontalOptions="End" />
</Grid>
<Grid RowDefinitions="*, Auto"
ColumnDefinitions="*, Auto"
HorizontalOptions="Fill"
HeightRequest="40"
Margin="30, 20, 30, 0">
<Label Text="Courses"
Grid.Row="0"
Grid.Column="0"
FontSize="15"
VerticalOptions="Center"
HorizontalOptions="Fill"
HorizontalTextAlignment="Start"
HeightRequest="20" />
<Button Grid.Row="0"
Grid.Column="1"
Text="&#xe3d4;"
FontFamily="Phosphor"
FontSize="15"
WidthRequest="20"
HeightRequest="20"
TextColor="AliceBlue"
BackgroundColor="Transparent"
HorizontalOptions="End"
VerticalOptions="Center"
Clicked="AddCourseButton_OnClicked"/>
</Grid>
<BoxView
Color="AliceBlue"
HeightRequest="2"
HorizontalOptions="Fill"
Margin="30, 0, 30, 20" />
<VerticalStackLayout x:Name="CourseButtonStack"
Margin="30,10"
HorizontalOptions="Fill"
Spacing="5"/>
</VerticalStackLayout>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WguApp.Controls;
using WguApp.Models;
using WguApp.Services;
namespace WguApp.Views;
public partial class TermPage : ContentPage
{
private Term _term;
private List<Course> _courses = [];
public TermPage(Term term)
{
InitializeComponent();
_term = term;
Title = string.IsNullOrWhiteSpace(_term.Name) ? "Term" : _term.Name;
TitleEntry.Text = _term.Name;
StartDatePicker.Date = _term.StartDate;
EndDatePicker.Date = _term.EndDate;
}
private async Task RefreshCourseList()
{
_courses.Clear();
_courses = await DatabaseService.GetCoursesByTermId(_term.Id);
CourseButtonStack.Children.Clear();
foreach (var course in _courses)
{
var btn = new CustomButton()
{
Text = course.Name,
DateText = $"{DateOnly.FromDateTime(course.StartDate).ToString()} - {DateOnly.FromDateTime(course.EndDate).ToString()}"
};
CourseButtonStack.Add(btn);
btn.Clicked += (sender, e) =>
{
HandleCourseButtonClick(course);
};
}
}
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
try
{
await RefreshCourseList();
}
catch (Exception e)
{
await DisplayAlert("Error", e.Message, "Ok");
}
}
private async void HandleCourseButtonClick(Course course)
{
await Navigation.PushAsync(new CoursePage(course));
}
private async void SaveButton_OnClicked(object? sender, EventArgs e)
{
var validationResult = ValidateFields();
if (!validationResult.result)
{
await DisplayAlert("Validation Error", validationResult.msg, "Ok");
return;
}
_term.Name = TitleEntry.Text;
_term.StartDate = StartDatePicker.Date;
_term.EndDate = EndDatePicker.Date;
var updateResult = await DatabaseService.UpdateTerm(_term);
if (updateResult)
{
await DisplayAlert("Info", $"{_term.Name} Saved!", "Ok");
}
else
{
await DisplayAlert("Error", "Could not save term", "Ok");
}
Title = _term.Name;
}
private (string msg, bool result) ValidateFields()
{
var message = string.Empty;
if (string.IsNullOrWhiteSpace(TitleEntry.Text)) message += "Title cannot be blank\n";
if (StartDatePicker.Date > EndDatePicker.Date) message += "Start Date cannot be after the End Date\n";
return string.IsNullOrWhiteSpace(message) ? (message, true) : (message, false);
}
private async void DeleteButton_OnClicked(object? sender, EventArgs e)
{
var result = await DisplayAlert("Delete Term", $"Do you really want to delete '{_term.Name}'? This action cannot be undone.", "Delete", "Cancel");
if (!result) return;
await DatabaseService.DeleteTerm(_term);
await Navigation.PopAsync();
}
private async void AddCourseButton_OnClicked(object? sender, EventArgs e)
{
if (_courses.Count >= 6)
{
await DisplayAlert("Info", $"Cannot add more than 6 courses per term", "Ok");
return;
}
var course = new Course()
{
TermId = _term.Id,
Name = "New Course",
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30),
InstructorName = "",
InstructorEmail = "",
InstructorPhone = "",
StartNotifCheck = false,
EndNotifCheck = false,
Status = CourseStatus.Planned,
Notes = ""
};
await DatabaseService.AddCourse(course);
await RefreshCourseList();
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Views.TestPage"
Title="Test">
<ContentPage.Content>
<VerticalStackLayout Spacing="10">
<Button
Text="Test notification"
Clicked="TestBtn_OnClicked"/>
<Button Text="List scheduled notifications"
Clicked="ListNotifs_Clicked"/>
<Button Text="Clear all notifications"
Clicked="ClearAllNotifs_Clicked"/>
<Button Text="Test Share"
Clicked="ShareButton_OnClick"/>
<Button
Text="Add sample data"
Clicked="AddData_OnCLicked"></Button>
<Button Text="Delete data"
Clicked="Delete_OnClicked"/>
<Button Text="Test logger"
Clicked="TestLogger_Clicked"/>
<Button Text="Show log file"
Clicked="ShowLog_Clicked"/>
<Button Text="Clear Log"
Clicked="ClearLog_Clicked"/>
</VerticalStackLayout>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Plugin.LocalNotification;
using WguApp.Services;
namespace WguApp.Views;
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
private async void TestBtn_OnClicked(object? sender, EventArgs e)
{
if (await LocalNotificationCenter.Current.AreNotificationsEnabled() == false)
{
await LocalNotificationCenter.Current.RequestNotificationPermission();
}
var notification = new NotificationRequest
{
NotificationId = 1,
Title = "Notification Test",
Description = "Success!",
Schedule =
{
NotifyTime = DateTime.Now.AddSeconds(30),
RepeatType = NotificationRepeat.No
}
};
await LocalNotificationCenter.Current.Show(notification);
await DisplayAlert("Info", "A test notification should appear in 30 seconds", "ok");
}
private async void AddData_OnCLicked(object? sender, EventArgs e)
{
var result = await DisplayAlert("Load Sample Data", "Do you really want to load DB sample data? This will delete ALL data from the database. This action cannot be undone.", "Yes", "No");
if (!result) return;
await DatabaseService.LoadSampleData();
var terms = await DatabaseService.GetAllTerms();
var courses = await DatabaseService.GetAllCourses();
var assessments = await DatabaseService.GetAllAssessments();
await DisplayAlert("Info", $"Tried to load sample db data, there are now {terms.Count} Terms, {courses.Count} Courses, and {assessments.Count} Assessments", "Ok");
}
private async void Delete_OnClicked(object? sender, EventArgs e)
{
var terms = await DatabaseService.GetAllTerms();
var courses = await DatabaseService.GetAllCourses();
var assessments = await DatabaseService.GetAllAssessments();
var result = await DisplayAlert("Delete DB Data", $"Do you really want to delete the DB data? This will delete ALL data from the database. This action cannot be undone.\nThere are currently {terms.Count} Terms, {courses.Count} Courses, and {assessments.Count} Assessments", "Yes", "No");
if (!result) return;
await DatabaseService.ClearDbData();
terms = await DatabaseService.GetAllTerms();
courses = await DatabaseService.GetAllCourses();
assessments = await DatabaseService.GetAllAssessments();
await DisplayAlert("Info", $"Tried to delete db data, there are now {terms.Count} Terms, {courses.Count} Courses, and {assessments.Count} Assessments", "Ok");
}
private async void ShareButton_OnClick(object? sender, EventArgs e)
{
await Share.Default.RequestAsync(new ShareTextRequest
{
Text = "Test Share Text",
Title = "Share Text"
});
}
private async void ListNotifs_Clicked(object? sender, EventArgs e)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
var message = pendingNotifs.Aggregate($"-- Scheduled notifications ({pendingNotifs.Count}) --\n",
(current, notif) => current + $"{notif.Title} : {notif.Schedule.NotifyTime}\n");
await DisplayAlert("Info", message, "Ok");
}
private void TestLogger_Clicked(object? sender, EventArgs e)
{
LoggerService.LogToFile("Test");
}
private async void ClearAllNotifs_Clicked(object? sender, EventArgs e)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
foreach (var notificationRequest in pendingNotifs)
{
await LocalNotificationService.CancelNotification(notificationRequest.NotificationId);
}
}
private async void ShowLog_Clicked(object? sender, EventArgs e)
{
var logFilePath = Path.Combine(FileSystem.AppDataDirectory, "log.txt");
await DisplayAlert("Log", await File.ReadAllTextAsync(logFilePath), "Ok");
}
private void ClearLog_Clicked(object? sender, EventArgs e)
{
var logFilePath = Path.Combine(FileSystem.AppDataDirectory, "log.txt");
File.WriteAllText(logFilePath, "");
}
}