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

3
WguApp/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
bin/
obj/
.idea/

0
WguApp/.noai Normal file
View File

14
WguApp/App.xaml Normal file
View File

@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WguApp"
x:Class="WguApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

25
WguApp/App.xaml.cs Normal file
View File

@@ -0,0 +1,25 @@
using WguApp.Services;
using WguApp.Views;
namespace WguApp;
public partial class App : Application
{
private HomePage _homePage = new();
private NavigationPage _navPage;
public App()
{
InitializeComponent();
if (Current != null) Current.UserAppTheme = AppTheme.Dark;
_navPage = new NavigationPage(_homePage);
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(_navPage);
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WguApp.Controls.CustomButton"
x:Name="ThisControl">
<Border>
<Grid BackgroundColor="#002f51"
Padding="10"
ColumnDefinitions="*, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0"
Text="{Binding Text, Source={x:Reference ThisControl}}"
FontSize="15"
VerticalOptions="Center"
TextColor="AliceBlue"
HorizontalOptions="Start"/>
<Label Grid.Column="1"
Text="{Binding DateText, Source={x:Reference ThisControl}}"
FontSize="12"
VerticalOptions="Center"
TextColor="Gray"
Margin="10,0,0,0"/> </Grid>
</Border>
</ContentView>

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WguApp.Controls;
public partial class CustomButton : ContentView
{
public event EventHandler? Clicked;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: nameof(Text),
returnType: typeof(string),
declaringType: typeof(CustomButton),
defaultValue: "Text",
defaultBindingMode: BindingMode.OneWay);
public static readonly BindableProperty DateTextProperty =
BindableProperty.Create(
propertyName: nameof(DateText),
returnType: typeof(string),
declaringType: typeof(CustomButton),
defaultValue: "//",
defaultBindingMode: BindingMode.OneWay);
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public string DateText
{
get => (string)GetValue(DateTextProperty);
set => SetValue(DateTextProperty, value);
}
public CustomButton()
{
InitializeComponent();
}
private void TapGestureRecognizer_OnTapped(object? sender, TappedEventArgs e)
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}

2
WguApp/GlobalXmlns.cs Normal file
View File

@@ -0,0 +1,2 @@
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "WguApp")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "WguApp.Pages")]

28
WguApp/MauiProgram.cs Normal file
View File

@@ -0,0 +1,28 @@
using Microsoft.Extensions.Logging;
using Plugin.LocalNotification;
namespace WguApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("Phosphor.ttf", "Phosphor");
fonts.AddFont("Phosphor-Fill.ttf", "Phosphor-Fill");
})
.UseLocalNotification();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

View File

@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using SQLite;
namespace WguApp.Models;
[Table("Assessments")]
public class Assessment
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int CourseId { get; set; }
public string Name { get; set; } = string.Empty;
public AssessmentType Type { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool StartNotifCheck { get; set; } = false;
public int StartNotifId { get; set; }
public bool EndNotifCheck { get; set; } = false;
public int EndNotifId { get; set; }
}
public enum AssessmentType
{
Performance, Objective
}

42
WguApp/Models/Course.cs Normal file
View File

@@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations.Schema;
using SQLite;
namespace WguApp.Models;
[SQLite.Table("Courses")]
public class Course
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int TermId { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public CourseStatus Status { get; set; }
public string InstructorName { get; set; } = string.Empty;
public string InstructorPhone { get; set; } = string.Empty;
public string InstructorEmail { get; set; } = string.Empty;
public bool StartNotifCheck { get; set; } = false;
public int StartNotifId { get; set; }
public bool EndNotifCheck { get; set; } = false;
public int EndNotifId { get; set; }
public string Notes { get; set; } = string.Empty;
public Course() { }
public Course(int termId, string name, DateTime startDate, DateTime endDate, CourseStatus status, string instructorName, string instructorPhone, string instructorEmail)
{
TermId = termId;
Name = name;
StartDate = startDate;
EndDate = endDate;
Status = status;
InstructorName = instructorName;
InstructorPhone = instructorPhone;
InstructorEmail = instructorEmail;
}
}
public enum CourseStatus
{
InProgress, Completed, Dropped, Planned
}

22
WguApp/Models/Term.cs Normal file
View File

@@ -0,0 +1,22 @@
using SQLite;
namespace WguApp.Models;
[Table("Terms")]
public class Term
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime StartDate {get; set;}
public DateTime EndDate {get; set;}
public Term() { }
public Term(string name, DateTime startDate, DateTime endDate)
{
Name = name;
StartDate = startDate;
EndDate = endDate;
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Optional: For exact timing -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="32" />
</manifest>

View File

@@ -0,0 +1,12 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace WguApp;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}

View File

@@ -0,0 +1,15 @@
using Android.App;
using Android.Runtime;
namespace WguApp;
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace WguApp;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
<dict>
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The Mac App Store requires you specify if the app uses encryption. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
<!-- Please indicate <true/> or <false/> here. -->
<!-- Specify the category for your app here. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
<!-- <key>LSApplicationCategoryType</key> -->
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace WguApp;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,16 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
namespace WguApp;
class Program : MauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
static void Main(string[] args)
{
var app = new Program();
app.Run(args);
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="9" xmlns="http://tizen.org/ns/packages">
<profile name="common" />
<ui-application appid="maui-application-id-placeholder" exec="WguApp.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
<label>maui-application-title-placeholder</label>
<icon>maui-appicon-placeholder</icon>
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
</ui-application>
<shortcut-list />
<privileges>
<privilege>http://tizen.org/privilege/internet</privilege>
</privileges>
<dependencies />
<provides-appdefined-privileges />
</manifest>

View File

@@ -0,0 +1,8 @@
<maui:MauiWinUIApplication
x:Class="WguApp.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:WguApp.WinUI">
</maui:MauiWinUIApplication>

View File

@@ -0,0 +1,23 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace WguApp.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="55663ABF-8FF6-41C6-A96E-913924D6F7A4" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="WguApp.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace WguApp;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace WguApp;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
You are responsible for adding extra entries as needed for your application.
More information: https://aka.ms/maui-privacy-manifest
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<!--
The entry below is only needed when you're using the Preferences API in your app.
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict> -->
</array>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "Project",
"nativeDebugging": false
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 228 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with your package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#ac99ea</Color>
<Color x:Key="PrimaryDarkText">#242424</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="SecondaryDarkText">#9880e5</Color>
<Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Magenta">#D600AA</Color>
<Color x:Key="MidnightBlue">#190649</Color>
<Color x:Key="OffBlack">#1f1f1f</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
</ResourceDictionary>

View File

@@ -0,0 +1,440 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style TargetType="ActivityIndicator">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="IndicatorView">
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
</Style>
<Style TargetType="Border">
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="StrokeShape" Value="Rectangle"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="BoxView">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Button">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="14,10"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="DatePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Editor">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ImageButton">
<Setter Property="Opacity" Value="1" />
<Setter Property="BorderColor" Value="Transparent"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Opacity" Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label" x:Key="Headline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="32" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Label" x:Key="SubHeadline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="24" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="ListView">
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Picker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RadioButton">
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RefreshView">
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="SearchBar">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SearchHandler">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Shadow">
<Setter Property="Radius" Value="15" />
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Offset" Value="10,10" />
</Style>
<Style TargetType="Slider">
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SwipeItem">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="Switch">
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="ThumbColor" Value="{StaticResource White}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="TimePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!--
<Style TargetType="TitleBar">
<Setter Property="MinimumHeightRequest" Value="32"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="TitleActiveStates">
<VisualState x:Name="TitleBarTitleActive">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="TitleBarTitleInactive">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
-->
<Style TargetType="Page" ApplyToDerivedTypes="True">
<Setter Property="Padding" Value="0"/>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
</Style>
<Style TargetType="Shell" ApplyToDerivedTypes="True">
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
<Setter Property="Shell.NavBarHasShadow" Value="False" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,230 @@

using SQLite;
using WguApp.Models;
namespace WguApp.Services;
public static class DatabaseService
{
private static SQLiteAsyncConnection? _db;
public static async Task Init()
{
if (_db is not null) return;
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "WguApp.db");
_db = new SQLiteAsyncConnection(databasePath);
try
{
await _db.CreateTablesAsync<Term, Course, Assessment>();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
// -- Add Methods -- //
#region Add methods
public static async Task<bool> AddTerm(Term term)
{
await Init();
var result = await _db?.InsertAsync(term)!;
return !(result <= 0);
}
public static async Task<bool> AddCourse(Course course)
{
await Init();
var result = await _db?.InsertAsync(course)!;
return !(result <= 0);
}
public static async Task<bool> AddAssessment(Assessment assessment)
{
await Init();
var result = await _db?.InsertAsync(assessment)!;
return !(result <= 0);
}
#endregion
// -- Get Methods -- //
#region Get Methods
public static async Task<List<Term>> GetAllTerms()
{
await Init();
var allTerms = await _db?.Table<Term>().ToListAsync()!;
return allTerms ?? [];
}
public static async Task<List<Course>> GetAllCourses()
{
await Init();
var allCourses = await _db?.Table<Course>().ToListAsync()!;
return allCourses ?? [];
}
public static async Task<List<Assessment>> GetAllAssessments()
{
await Init();
var allAssessments = await _db?.Table<Assessment>().ToListAsync()!;
return allAssessments ?? [];
}
public static async Task<List<Assessment>> GetAssessmentsByCourseId(int courseId)
{
await Init();
var query = $"SELECT * FROM Assessments WHERE CourseId = {courseId}";
var result = await _db?.QueryAsync<Assessment>(query)!;
return result.ToList() ?? [];
}
public static async Task<List<Course>> GetCoursesByTermId(int termId)
{
await Init();
var query = $"SELECT * FROM Courses WHERE TermId = {termId}";
var result = await _db?.QueryAsync<Course>(query)!;
return result.ToList() ?? [];
}
#endregion
// -- Update Methods
#region Update Methods
public static async Task<bool> UpdateTerm(Term term)
{
await Init();
return await _db?.UpdateAsync(term)! != 0;
}
public static async Task<bool> UpdateCourse(Course course)
{
await Init();
return await _db?.UpdateAsync(course)! != 0;
}
public static async Task<bool> UpdateAssessment(Assessment assessment)
{
await Init();
return await _db?.UpdateAsync(assessment)! != 0;
}
#endregion
// -- Delete Methods
#region Delete Methods
public static async Task<bool> DeleteTerm(Term term)
{
await Init();
return await _db?.DeleteAsync(term)! != 0;
}
public static async Task<bool> DeleteTerm(int termId)
{
await Init();
return await _db?.DeleteAsync<Term>(termId)! != 0;
}
public static async Task<bool> DeleteCourse(Course course)
{
await Init();
return await _db?.DeleteAsync(course)! != 0;
}
public static async Task<bool> DeleteCourse(int courseId)
{
await Init();
return await _db?.DeleteAsync<Course>(courseId)! != 0;
}
public static async Task<bool> DeleteAssessment(Assessment assessment)
{
await Init();
return await _db?.DeleteAsync(assessment)! != 0;
}
public static async Task<bool> DeleteAssessment(int assessmentId)
{
await Init();
return await _db?.DeleteAsync<Assessment>(assessmentId)! != 0;
}
#endregion
// -- Sample Data -- //
public static async Task LoadSampleData()
{
await ClearDbData();
var term1 = new Term()
{
Name = "Term 1",
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30)
};
await AddTerm(term1);
var course1 = new Course()
{
TermId = term1.Id,
Name = "Course 1",
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30),
InstructorName = "Anika Patel",
InstructorEmail = "anika.patel@strimeuniversity.edu",
InstructorPhone = "555-123-4567",
StartNotifCheck = false,
EndNotifCheck = false,
Status = CourseStatus.InProgress,
Notes = "Some notes"
};
await AddCourse(course1);
var assessment1 = new Assessment()
{
CourseId = course1.Id,
Name = "Performance Assessment",
Type = AssessmentType.Performance,
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30),
StartNotifCheck = false,
EndNotifCheck = false
};
await AddAssessment(assessment1);
var assessment2 = new Assessment()
{
CourseId = course1.Id,
Name = "Objective Assessment",
Type = AssessmentType.Objective,
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30),
StartNotifCheck = false,
EndNotifCheck = false
};
await AddAssessment(assessment2);
}
public static async Task ClearDbData()
{
await Init();
await _db?.DeleteAllAsync<Assessment>()!;
await _db?.DeleteAllAsync<Course>()!;
await _db?.DeleteAllAsync<Term>()!;
_db = null;
}
}

View File

@@ -0,0 +1,114 @@
using Plugin.LocalNotification;
namespace WguApp.Services;
public static class LocalNotificationService
{
private static readonly Random Rng = new();
public static async Task<int?> ScheduleNotification(string title, string description, DateTime time)
{
var permCheck = await PermissionsService.CheckNotificationPermissions();
if (!permCheck)
{
LoggerService.LogToFile("Notification Service: Notification Permission Not Granted");
return null;
}
var newId = Rng.Next();
var notif = new NotificationRequest
{
NotificationId = newId,
Title = title,
Description = description,
Schedule =
{
NotifyTime = time,
RepeatType = NotificationRepeat.No
}
};
try
{
await LocalNotificationCenter.Current.Show(notif);
LoggerService.LogToFile($"Notification Service: {notif.NotificationId}:{notif.Title} set");
}
catch (Exception e)
{
LoggerService.LogToFile($"Notification Service: {e.Message}");
throw;
}
return notif.NotificationId;
}
public static async Task<int> ScheduleNotification(NotificationRequest notif)
{
try
{
await LocalNotificationCenter.Current.Show(notif);
}
catch (Exception e)
{
LoggerService.LogToFile(e.Message);
throw;
}
return notif.NotificationId;
}
public static async Task<bool> UpdateNotification(int notificationId, string? title = null, string? description = null, DateTime? time = null)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
var notif = pendingNotifs.FirstOrDefault(n => n.NotificationId == notificationId);
if (notif is null) return false;
await CancelNotification(notif);
notif.Title = title ?? notif.Title;
notif.Description = description ?? notif.Description;
notif.Schedule = new NotificationRequestSchedule()
{
NotifyTime = time ?? notif.Schedule.NotifyTime,
RepeatType = NotificationRepeat.No
};
await ScheduleNotification(notif);
return true;
}
public static async Task<bool> CancelNotification(int notificationId)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
var match = pendingNotifs.FirstOrDefault(n => n.NotificationId == notificationId);
if (match is null) return false;
LocalNotificationCenter.Current.Cancel(notificationId);
return true;
}
public static async Task<bool> CancelNotification(NotificationRequest notif)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
if (pendingNotifs.Contains(notif))
{
notif.Cancel();
}
return true;
}
public static async Task<bool> DoesNotificationAlreadyExist(int notificationId)
{
var pendingNotifs = await LocalNotificationCenter.Current.GetPendingNotificationList();
var match = pendingNotifs.FirstOrDefault(n => n.NotificationId == notificationId);
return match is not null;
}
}

View File

@@ -0,0 +1,11 @@
namespace WguApp.Services;
public class LoggerService
{
public static void LogToFile(string text)
{
var logFilePath = Path.Combine(FileSystem.AppDataDirectory, "log.txt");
File.AppendAllText(logFilePath, $"{DateTime.UtcNow} : {text}\n");
}
}

View File

@@ -0,0 +1,21 @@
using Plugin.LocalNotification;
namespace WguApp.Services;
public static class PermissionsService
{
public static async Task<bool> CheckNotificationPermissions()
{
if (await LocalNotificationCenter.Current.AreNotificationsEnabled()) return true;
try
{
return await LocalNotificationCenter.Current.RequestNotificationPermission();
}
catch (Exception e)
{
LoggerService.LogToFile(e.Message);
return false;
}
}
}

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, "");
}
}

70
WguApp/WguApp.csproj Normal file
View File

@@ -0,0 +1,70 @@
<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> -->
<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
<OutputType>Exe</OutputType>
<RootNamespace>WguApp</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Display name -->
<ApplicationTitle>WguApp</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.chrisbell.wguapp</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>
<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>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128"/>
<!-- Images -->
<MauiImage Include="Resources\Images\*"/>
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*"/>
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)"/>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.8"/>
<PackageReference Include="Plugin.LocalNotification" Version="12.0.2" />
<PackageReference Include="sqlite-net-pcl" Version="1.10.196-beta" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.11" />
</ItemGroup>
</Project>

16
WguApp/WguApp.sln Normal file
View File

@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WguApp", "WguApp.csproj", "{BB2F04F1-B5E0-4852-8AAA-AD1AB18D7011}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BB2F04F1-B5E0-4852-8AAA-AD1AB18D7011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB2F04F1-B5E0-4852-8AAA-AD1AB18D7011}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB2F04F1-B5E0-4852-8AAA-AD1AB18D7011}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB2F04F1-B5E0-4852-8AAA-AD1AB18D7011}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADateOnly_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003Fchris_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb7bf1a4162ee5b47e01ff05d38423e295c7bbb45f3c7b3b77ab51d949e7e741b_003FDateOnly_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>