write unit tests: https://bunit.dev/ https://github.com/bUnit-dev/bUnit
https://learn.microsoft.com/en-us/samples/dotnet/maui-samples/uitest-appium-nunit/ https://github.com/dotnet/maui-samples/tree/main/8.0/UITesting/BasicAppiumNunitSample
https://devblogs.microsoft.com/dotnet/dotnet-maui-ui-testing-appium/ https://github.com/jfversluis/Template.Maui.UITesting
find out why padding-left: 12px !important;
is needed on iOS - try: padding-left: env(safe-area-inset-left) !important;
fix AppData GetUserData() which calls InitializeContent()
search for // TODO:: remove temp fix
InitializeItems and InitializeTimes have null checks and do not update data when called in GetUserData()
both load data directly from DB with _dataAccess.GetTimes() and _dataAccess.GetItems()
but LoadTimesDone also loads data with _dataAccess.GetTimes() - these are not the same objects as in InitializeTimes
and ItemService.Initialize also loads data with _dataAccess.GetItems() - these are not the same objects as in InitializeItems
user can add or remove Items and Times list but the code does not update Items and Times in the AppData
so without temp fix, GetUserData() would return Items and Times that were loaded with Initialize()
remove these from class AppData:
public Dictionary<long, TimeModel>? Times { get; set; }
public Dictionary<long, ItemModel>? Items { get; set; }
or make sure that other services update them
this is a big problem - services use _dataAccess on their own, but AppData is supposed to represent the current state - as the only source of truth
Ididit did not have this problem, Repository
was the only class with IDatabaseAccess
and represented the current state
add a migration for the User
recreate initial migration in OpenHabitTracker.Blazor.Web
UserData -> UserModel - add Settings and Categories to UserModel AppData public Initialize() => local methods: InitializeUser, InitializeSettings, InitializePriorities
make every ...Id a required field in EF Core - Debug.Assert(Id != 0) before Add / Update
refactor OpenHabitTracker 4. run Jetbrains Rider code analysis 5. add comments to methods 6. add REST API endpoints for online data sync to Blazor Server - use them in Blazor Wasm, Photino, Wpf, WinForms, Maui
add OAuth to Blazor Wasm, Photino, Wpf, WinForms, Blazor Server, Maui Google Drive Microsoft OneDrive Dropbox
use Google, Microsoft, Dropbox OAuth for unique user id and login
add backup to Google Drive Microsoft OneDrive Dropbox
use DB in Blazor Server for multi user sync with add REST API endpoints
write unit tests with Appium / bUnit
Android: get permission to save SQLite DB in an external folder that can be part of Google Drive, OneDrive, iCloud, Dropbox
deploy Blazor Server Docker image to Raspberry Pi 5 / Synology NAS DS224+
add Google Drive Blazor WASM -> Google Drive REST API Blazor Desktop -> Google Drive API add Blazor Server - OAuth REST, CRUD REST, SignalR for instant UI refresh on multiple devices Blazor Mobile -> Blazor Server Blazor Server -> Google Drive API
login will be with Google, Microsoft, Dropbox - requires scope with permission to get email email will be unique user id store the refresh token for each cloud provider
!!! add comments to methods - 1. for any open source contributor - 2. for GitHub Copilot
!!! run Jetbrains Rider code analysis
!!! refactor classes:
AppData, CalendarParams, IRuntimeData - not in Data(base) namespace
UserData -> UserImportExportData
AppData -> ClientSideData - hold state - load state - map to models - interact with _dataAccess - interact with _runtimeData - interact with _markdownPipeline - import, export / GetUserData, SetUserData - LoadExamples
IRuntimeData -> IClientSideRuntimeData
Initialize -> LoadSavedData
ClientSideState: public Dictionary<long, HabitModel>? Habits { get; set; } public Dictionary<long, NoteModel>? Notes { get; set; } public Dictionary<long, TaskModel>? Tasks { get; set; } public Dictionary<long, TimeModel>? Times { get; set; } public Dictionary<long, ItemModel>? Items { get; set; } public Dictionary<long, CategoryModel>? Categories { get; set; } public Dictionary<long, PriorityModel>? Priorities { get; set; }
Android: save SQLite DB in an external folder can be part of Google Drive, OneDrive, iCloud, Dropbox
AndroidManifest.xml MANAGE_EXTERNAL_STORAGE
using System; using System.IO; using System.Runtime.InteropServices; using Android.Content.PM; using Android.OS; using Xamarin.Essentials; using Android; using Android.Content.PM; using Android.Support.V4.App; using Android.Support.V4.Content;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // LocalApplicationData, ApplicationData, UserProfile, Personal, MyDocuments, Desktop, DesktopDirectory { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) // LocalApplicationData, ApplicationData, UserProfile, Personal, MyDocuments, Desktop, DesktopDirectory { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Android)) { if (ContextCompat.CheckSelfPermission(Android.App.Application.Context, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted) { ActivityCompat.RequestPermissions(MainActivity.Instance, new string[] { Manifest.Permission.WriteExternalStorage }, 1); } path = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "MyAppFolder"); return Path.Combine(Android.OS.Environment.ExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments).AbsolutePath, "MyAppFolder"); } if (RuntimeInformation.IsOSPlatform(OSPlatform.iOS)) { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); }
setup Authentication @* @ @ *@ move LoginDisplay / @NavBarFragment.GetNavBarFragment() to Backup appsettings.json appsettings.Development.json
https://github.com/openiddict/openiddict-core
https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
Backup
Google Drive https://www.nuget.org/packages/Google.Apis.Drive.v3
OneDrive https://www.nuget.org/packages/Microsoft.Graph
iCloud --- https://github.com/gachris/iCloud.Dav ---
Dropbox https://www.nuget.org/packages/Dropbox.Api
WASM authorisation - REST
desktop authorisation - OpenHabitTracker.Google.Apis - using Google.Apis.Auth.OAuth2;
mobile authorisation - ASP.NET Core
Nextcloud https://www.nuget.org/packages/NextcloudApi ownCloud https://www.nuget.org/packages/bnoffer.owncloudsharp
Blazor Server / Web
ASP.NET Core
SQL Server
version history: https://learn.microsoft.com/en-us/ef/core/providers/sql-server/temporal-tables
table Users
column UserId in every other table
EF Core: use DbContextFactory
Host 24/7 on Raspberry Pi 5 Synology NAS DS224+
when all habit items are done, habit is done when all task items are done, task is done
content background: list all possible colors whole
repeat:
add StartAt
/ PlannedAt
to Habit ? some starting point for repeat interval
weekly: which day in week
monthly: which day (or week/day - second monday) in month
yearly: which day (date) in year
textarea Tabs make markdown Tabs look the same as in textarea insert Tabs in multiple rows
Show only habits with ratio over
/ under
horizontal calendar with vertical weeks
replace all @inject AppData AppData
with appropriate services
call LoadTimesDone on Habit Initialize - sort needs it, every calendar needs it, ... save TotalTimeSpent save AverageInterval on Habit Initialize - load only last week (last X days, displayed in small calendar) call LoadTimesDone for large calendar
benchmark method time & render time
read Settings from DB before Run() - !!! Transient / Scoped / Singleton !!!
??? Task CompletedAt
/ Habit LastTimeDoneAt
--> DateTime? DoneAt
???
common Router
OpenHabitTracker.Blazor - Routes.razor
OpenHabitTracker.Blazor.Wasm - App.razor - CascadingAuthenticationState, AuthorizeRouteView, NotAuthorized
OpenHabitTracker.Blazor.Server: - @page "/Error" - app.UseExceptionHandler("/Error");
Google Keep - title - pin - note - reminder - date - time - place - repeat - Does not repeat - Daily - Weekly - Monthly - Yearly - Custom: - Forever - Until a date - For a number of events - collaborator - background - (app) take photo - add image - archive - delete - add label - add drawing - (app) recording - make copy - show checkboxes - (app) send (share) - copy to Google Docs - version history - undo - redo - close - (app): - h1 - h2 - normal text - bold - italic - underline - clear () text (T) formatting
- filters are query parameters
copy Loop Habit Tracker
- History (done count grouped by week, month, quarter, year)
- Calendar (continuous year calendar, no breaks in months: 7 days -> 7 rows (horizontal scroll) or 7 columns (vertical scroll))
- Best streaks (from date - to date)
- Frequency (by day of the week - continuous calendar, without dates, done count grouped by days of the week)
-
drag & drop reorder
-
keyboard navigation
-
benchmark: method time & render time
-
ASAP tasks: when, where, contact/company name, address, phone number, working hours, website, email
-
don't use
event
to refresh everything on every change -
don't use
StateHasChanged()
-
don't do this: current screen changed -> save current screen to settings -> data changed -> refresh all
email: copy task list as HTML with checkboxes to clipboard sms, message: copy task list with Unicode checkboxes
virtualized container
method trace logging - benchmark method performance https://learn.microsoft.com/en-us/aspnet/core/blazor/performance
what is wrong: I'm not doing the critical tasks - because I see too many unimportant tasts that are overdue and I am satisfied with completing them